Added RFC1006 as separate library
[yaz-moved-to-github.git] / rfc1006 / rfct.c
1 /*
2  * 1995, Index Data I/S 
3  * 
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: rfct.c,v $
7  * Revision 1.1  1995-03-30 14:03:17  quinn
8  * Added RFC1006 as separate library
9  *
10  * Revision 1.15  1995/03/30  10:54:43  quinn
11  * Fiddling with packet sizes, trying to help ISODE.. :(
12  *
13  * Revision 1.14  1995/03/27  08:36:07  quinn
14  * Some work on nonblocking operation in xmosi.c and rfct.c.
15  * Added protocol parameter to cs_create()
16  *
17  * Revision 1.13  1995/03/20  11:27:16  quinn
18  * Fixed bug in the _t_rcv stuff
19  *
20  * Revision 1.12  1995/03/20  09:54:07  quinn
21  * Debugging session
22  *
23  * Revision 1.11  1995/03/20  09:47:16  quinn
24  * Added server-side support to xmosi.c
25  * Fixed possible problems in rfct
26  * Other little mods
27  *
28  * Revision 1.10  1995/03/17  19:37:26  quinn
29  * *** empty log message ***
30  *
31  * Revision 1.9  1995/03/17  19:28:32  quinn
32  * Working on fixing our mystery-bug.
33  *
34  * Revision 1.8  1995/03/14  10:28:38  quinn
35  * Adding server-side support to tcpip.c and fixing bugs in nonblocking I/O
36  *
37  * Revision 1.7  1995/03/09  18:42:32  quinn
38  * Fixed another bug in t_rcv
39  *
40  * Revision 1.6  1995/03/09  15:22:42  quinn
41  * Fixed two bugs in get/rcv
42  *
43  * Revision 1.5  1995/03/07  16:29:46  quinn
44  * Various fixes.
45  *
46  * Revision 1.4  1995/03/06  16:48:03  quinn
47  * Smallish changes.
48  *
49  * Revision 1.3  1995/03/06  10:54:32  quinn
50  * Server-side functions (t_bind/t_listen/t_accept) seem to work ok, now.
51  * Nonblocking mode needs work (and testing!)
52  * Added makensap to replace function in mosiutil.c.
53  *
54  * Revision 1.2  1995/03/03  09:40:36  quinn
55  * Added most of the remaining functionality.
56  * Still need exstensive testing of server-side functions and nonblocking
57  * I/O.
58  *
59  * Revision 1.1  1995/03/01  08:40:33  quinn
60  * First working version of rfct. Addressing needs work.
61  *
62  */
63
64 /*
65  * Simple implementation of XTI/TP0/RFC1006/Sockets.
66  * Note: This is neither complete nor robust. It just has to tick us over
67  * until mr. Furniss finishes his own implementation.
68  *
69  * TODO: Asynchronous mode needs a lot of little adjustments to various
70  * return values and error codes, etc.
71  *
72  * Check if addressing info is returned correctly by all calls.
73  */
74
75 #include <stdio.h>
76 #include <string.h>
77 #include <sys/types.h>
78 #include <sys/param.h>
79 #include <sys/socket.h>
80 #include <netdb.h>
81 #include <netinet/in.h>
82 #include <arpa/inet.h>
83
84 #include <assert.h>
85 #include <stdlib.h>
86 #include <unistd.h>
87 #include <sys/uio.h>
88 #include <errno.h>
89 #include <fcntl.h>
90
91 #include <xti.h>
92 #include <fcntl.h>
93 #ifdef __linux__
94 #include <linux/limits.h>
95 #endif
96 #include <dmalloc.h>  /* project memory debugging - delete if you don't have it */
97
98 #ifdef TRACE_TRANSPORT
99 #define TRC(x) x
100 #else
101 #define TRC(X)
102 #endif
103
104 #define TSEL_MAXLEN 20   /* is there a standard for this? */
105 #define RFC_DEFAULT_PORT 4500
106 #define MAX_QLEN 5        /* outstanding connect indications */
107
108 static int t_look_wait(int fd, int wait);
109 int t_rcvconnect(int fd, struct t_call *call);
110
111 int t_errno = 0;
112 int xti_trace = 0;
113
114 static struct rfct_control
115 {
116     int state;          /* state of transport endpoint */
117     int event;          /* current event */
118     int tsize;          /* max TPDU size */
119     int curcode;        /* code of current  TPDU */
120     int pending;        /* # bytes of user data waiting on line */
121     int eot_flag;       /* current incoming TPDU had EOT set */
122     int hlen;           /* TPDU header suffix remaining on line */
123     int eot;            /* current incoming DATA TPDU is last in TSDU */
124     int togo;           /* # bytes waiting to go on current outgoing DATA */
125     int qlen;           /* set by t_bind */
126     int listen;         /* we are listening */
127     int tmpfd;          /* holding space for client between listen and accept */
128     int flags;          /* set by t_open */
129     char rref[2];       /* remote reference */
130     char ltsel[TSEL_MAXLEN]; /* local transport selector */
131     int ltsel_len;
132     int oci[MAX_QLEN];   /* outstanding connect indications */
133 } *control[NOFILE];
134
135 /*
136  * In the best tradition of ThinOSI, we combine the RFC1006 and the common
137  * part of the TPDU header into one. Given the complexity of the transport
138  * layer, maybe it would have been nice to have it in a separate module
139  * for reuse - but such is hindsight.
140  * Using a bit-field like this may be dangerous. I would expect it to work
141  * fine on any 32-bit or 64-bit machine, with any decent compiler. A DOS
142  * compiler might make a mess of it, I suppose, but I wouldn't even expect
143  * that.
144  */
145 struct rfc_header
146 {
147     /* RFC1006 */
148     unsigned version:8;
149 #define RFC_VERSION 3
150     unsigned reserved:8;
151     unsigned len:16;         /* length of entire package, including header */
152     /* TPDU common fields */
153     unsigned char hlen;     /* length of TPDU-header minus length-octet */
154     unsigned char code;     /* TPDU code (and an unused 4-bit field) */
155     char suffix[100];       /* unstructured TPDU elements, for convenience */
156 };
157
158 /*
159  * The TPDU-codes used by this package.
160  */
161 #define TPDU_CODE_CREQ 0xe0
162 #define TPDU_CODE_CCON 0xd0
163 #define TPDU_CODE_DATA 0xf0
164 #define TPDU_CODE_DREQ 0x80
165
166 /*
167  * Parameters that we care about.
168  */
169 #define TPDU_PARM_TSIZE 0xc0  /* max TPDU size */
170 #define TPDU_PARM_CLGID 0xc1  /* Calling TSAP-ID */
171 #define TPDU_PARM_CLDID 0xc2  /* Called TSAP-ID */
172
173 struct tpdu_connect_header   /* length of fixed suffix: 5 octets */
174 {
175     char dst_ref[2];     /* 3-4 - not used by TP0 */
176     char src_ref[2];     /* 5-6 - not used by TP0 */
177     char class;        /* 7 - always 0 */
178 };
179
180 struct tpdu_disconnect_header /* length of fixed suffix: 5 octets */
181 {
182     char dst_ref[2];
183     char src_ref[2];
184     char reason;
185 };
186
187 struct tpdu_data_header      /* length of fixed suffix: 1 octet */
188 {
189     unsigned char nr;       /* we only use bit 7 (1 << 7), to mark EOT */
190 #define DATA_EOT (1<<7)
191 };
192
193 static int rfc_running = 0; /* Have we been initialized? */
194
195 static void init_rfc(void)
196 {
197     int i;
198
199     for (i = 0; i < NOFILE; i++)
200         control[i] = 0;
201     rfc_running = 1;
202 }
203
204 int t_open(char *name, int oflag, struct t_info *info)
205 {
206     struct rfct_control *cnt;
207     struct protoent *proto;
208     int s, i;
209
210     TRC(fprintf(stderr, "T_OPEN\n"));
211
212     if (!rfc_running)
213         init_rfc();
214
215     if (!(proto = getprotobyname("tcp")))
216         return 0;
217     if ((s = socket(AF_INET, SOCK_STREAM, proto->p_proto)) < 0)
218         return 0;
219 #ifdef NONBLOCKING_OSI
220     if ((oflag & O_NONBLOCK) && fcntl(s, F_SETFL, O_NONBLOCK) < 0)
221     {
222         t_errno = TSYSERR;
223         return -1;
224     }
225 #endif
226     if (!(cnt = control[s] = malloc(sizeof(struct rfct_control))))
227     {
228         TRC(perror("malloc()"));
229         t_errno = TSYSERR;
230         return -1;
231     }
232
233     cnt->togo = 0;
234     cnt->pending = 0;
235     cnt->state = T_UNBND;
236     cnt->event = 0;
237     cnt->listen = 0;
238     cnt->qlen = 0;
239     cnt->flags = oflag;
240     cnt->tmpfd = -1;
241     cnt->ltsel_len = 0;
242     for (i = 0; i < MAX_QLEN; i++)
243         cnt->oci[i] = -1;
244
245     /*
246      * RFC1006 sets a higher than standard default max TPDU size, but the
247      * Isode seems to like to negotiate it down. We'll keep it here to be
248      * safe. Not that there's no harm in jumping it up. If it's higher
249      * than 2048, t_connect won't try to negotiate.
250      */
251     cnt->tsize = 128;
252
253     if (info)
254     {
255         info->addr = TSEL_MAXLEN + sizeof(struct sockaddr_in) + 1;
256         info->options = 1024;
257         info->tsdu = -1;
258         info->etsdu = 0;
259         info->connect = -2;
260         info->discon = -2;
261         info->servtype = T_COTS_ORD;  /* lets hope our user doesn't
262                                         try something funny. */
263     }
264     return s;
265 }
266
267 int t_connect(int fd, struct t_call *sndcall, struct t_call *rcvcall)
268 {
269     struct rfct_control *cnt = control[fd];
270     struct sockaddr_in addr;
271     char *p;
272     struct iovec vec[3]; /* RFC1006 header + T-header + parms */
273     struct rfc_header rfc;
274     struct tpdu_connect_header tpdu;
275     unsigned char pbuf[2 + TSEL_MAXLEN + 3]; /* CR parameters */
276     int plen = 0;
277
278     TRC(fprintf(stderr, "T_CONNECT\n"));
279     if (!cnt || cnt->state != T_IDLE)
280     {
281         TRC(fprintf(stderr, "TOUTSTATE\n"));
282         t_errno = TOUTSTATE;
283         return -1;
284     }
285     /* take the address apart */
286     p = sndcall->addr.buf;
287     if (*p) /* transport selector */
288     {
289         TRC(fprintf(stderr, "Tsel length is %d.\n", *p));
290         pbuf[0] = TPDU_PARM_CLDID;
291         pbuf[1] = *p;
292         memcpy(pbuf + 2, p + 1, *p);
293         plen = *p + 2;
294     }
295     p += *p + 1; /* skip tsel */
296     if (*p != sizeof(addr))
297     {
298         TRC(fprintf(stderr, "Expected  sockaddr here.\n"));
299         t_errno = TBADADDR;
300         return -1;
301     }
302     p++;
303     memcpy(&addr, p, sizeof(addr));
304     if (connect(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
305     {
306         t_errno = TSYSERR;
307         return -1;
308     }
309
310     /*
311      * If the default set by t_open() is higher than 2048, we don't try
312      * to negotiate, hoping that the opponent goes by the high default
313      * set by RFC1006. Otherwise, we just go for 2048 (max according to
314      * transport standard). The rest of this module doesn't really care
315      * about the size (although it's respected, of course), since we do
316      * no internal buffering.
317      */
318     if (cnt->tsize <= 2048)
319     {
320         pbuf[plen++] = TPDU_PARM_TSIZE;
321         pbuf[plen++] = 1;
322         pbuf[plen++] = 0x0b; /* request max PDU size (2048 octets) */
323     }
324
325     rfc.version = RFC_VERSION;
326     rfc.reserved = 0;
327     rfc.len =  htons(4 + 7 + plen);
328     rfc.hlen = 6 + plen;
329     rfc.code = TPDU_CODE_CREQ;
330
331     memset(tpdu.dst_ref, 0, 2);
332     memset(tpdu.src_ref, 0, 2);
333     tpdu.class = 0;
334
335     vec[0].iov_base = (caddr_t) &rfc;
336     vec[0].iov_len = 6;
337     vec[1].iov_base = (caddr_t) &tpdu;
338     vec[1].iov_len = 5;
339     vec[2].iov_base = (caddr_t) pbuf;
340     vec[2].iov_len = plen;
341
342     /*
343      * we don't expect flow-control problems on the first outgoing packet,
344      * though I suppose it'd be possible on some weird types of link.
345      */
346     if (writev(fd, vec, 3) < 4 + 7 + plen)
347     {
348         TRC(fprintf(stderr, "writev came up short. Aborting connect\n"));
349         t_errno = TSYSERR;
350         return -1;
351     }
352     cnt->state = T_OUTCON;
353     cnt->event = 0;
354     return t_rcvconnect(fd, rcvcall);
355 }
356
357 /*
358  * This needs work for asynchronous mode.
359  */
360 static int read_n(int fd, char *buf, int toget)
361 {
362     struct rfct_control *cnt = control[fd];
363     int res, got = 0;
364
365     do
366     {
367         if ((res = read(fd, buf, toget - got)) < 0)
368         {
369             if (errno == EAGAIN)
370                 t_errno = TNODATA;
371             else
372             {
373                 TRC(fprintf(stderr, "Error on read.\n"));
374                 t_errno = TSYSERR;
375             }
376             return -1;
377         }
378         if (!res) /* peer closed network connection */
379         {
380             t_errno = TLOOK;
381             cnt->event = T_DISCONNECT;  /* is this correct ? ## */
382             cnt->hlen = cnt->pending = 0;
383             cnt->state = T_IDLE; /* this can't be correct ## */
384             return 0;
385         }
386         buf += res;
387     }
388     while ((got += res) < toget);
389     return toget;
390 }
391
392 int t_rcvconnect(int fd, struct t_call *call)
393 {
394     struct rfct_control *cnt = control[fd];
395     struct tpdu_connect_header chead;
396     char buf[100], *p, *addrp;
397     struct sockaddr_in peer;
398     int len = sizeof(struct sockaddr_in);
399
400     TRC(fprintf(stderr, "T_RCVCONNECT\n"));
401     if (!cnt || cnt->state != T_OUTCON)
402     {
403         TRC(fprintf(stderr, "TOUTSTATE\n"));
404         t_errno = TOUTSTATE;
405         return -1;
406     }
407     if (!cnt->event)
408         if (t_look_wait(fd, 1) <= 0)
409             return -1;
410     if (cnt->event != T_CONNECT)
411     {
412         t_errno = TLOOK;
413         return -1;
414     }
415     /* read the rest of the CC TPDU */
416     if (read_n(fd, buf, cnt->hlen) <= 0)
417         return -1;
418     memcpy(&chead, buf, 5);
419     if (chead.class != 0)
420     {
421         TRC(fprintf(stderr, "Expected TP0, got %d\n", (int)chead.class));
422         t_errno = TSYSERR;
423         return -1;
424     }
425     if (call)
426         *(addrp = call->addr.buf) = 0;
427     cnt->hlen -= 5;
428     for (p = buf + 5; cnt->hlen > 0;)
429     {
430         switch ((unsigned char)*p)
431         {
432             case TPDU_PARM_TSIZE:
433                 cnt->tsize = 1 << *(p + 2);
434                 break;
435             case TPDU_PARM_CLDID:
436                 if (call)
437                 {
438                     if (*(p + 1) > TSEL_MAXLEN)
439                     {
440                         TRC(fprintf(stderr, "Called TSEL too long.\n"));
441                         t_errno = TSYSERR; /* Wrong.. ## */
442                         return -1;
443                     }
444                     *addrp = *(p + 1); /* length */
445                     memcpy(addrp + 1, p + 2, *(p + 1)); /* remote TSEL */
446                     addrp += *(p + 1); /* move past TSEL */
447                 }
448                 break;
449             case TPDU_PARM_CLGID: break; /* ignoring this for now */
450             default:
451                 TRC(fprintf(stderr, "Inoring CR parameter: %d\n",
452                     (unsigned char)*p));
453             /* we silently ignore anything else */
454         }
455         p++;
456         cnt->hlen -= (unsigned char) *p + 2;
457         p += (unsigned char) *p + 1;
458     }
459     addrp++; /* skip to end of addr + 1 */
460     if (call)
461     {
462         if (getpeername(fd, (struct sockaddr*) &peer, &len) < 0)
463         {
464             TRC(perror("getpeername()"));
465             t_errno = TSYSERR;
466             return -1;
467         }
468         *(addrp++) = sizeof(struct sockaddr_in);
469         memcpy(addrp, &peer, sizeof(struct sockaddr_in));
470         addrp += sizeof(struct sockaddr_in);
471         call->addr.len = addrp - call->addr.buf + 1;
472     }
473     cnt->state = T_DATAXFER;
474     cnt->event = 0;
475     return 0;
476 }
477
478 int t_snd(int fd, char *buf, unsigned nbytes, int flags)
479 {
480     struct rfct_control *cnt = control[fd];
481     struct rfc_header rfc;
482     struct iovec vec[2]; /* RFC1006 header + T-header + (user data) */
483     int writ = 0, res, towrite, eot;
484     int head_offset;
485
486     TRC(fprintf(stderr, "T_SND [%d bytes, flags %d]\n", nbytes, flags));
487     if (!cnt || cnt->state != T_DATAXFER)
488     {
489         TRC(fprintf(stderr, "Trying to write in the wrong state on fd %d.\n",
490             fd));
491         t_errno = TOUTSTATE;
492         return -1;
493     }
494     if (!nbytes)
495     {
496         t_errno = TBADDATA;
497         return -1;
498     }
499     do /* write the TSDU (segment) in chunks depending on the TPDU max size */
500     {
501         if (cnt->togo > 0) /* we have a partial TPDU on the wire */
502         {
503             TRC(fprintf(stderr, "  writing continuation block (%d)\n",
504                 cnt->togo));
505             if ((res = write(fd, buf, cnt->togo)) < 0)
506             {
507                 if (errno == EAGAIN)
508                 {
509                     t_errno = TFLOW;
510                     return -1;
511                 }
512                 cnt->togo -= res;
513                 return res;
514             }
515             writ += res;
516             cnt->togo = 0;
517             TRC(fprintf(stderr, "  wrote %d, total %d\n", res, writ));
518         }
519         else /* prepare and send (possibly partial) header */
520         {
521             towrite = nbytes - writ;
522             if (towrite + 3 + 4 > cnt->tsize)
523                 towrite = cnt->tsize - (3 + 4); /* space for DATA header */
524             rfc.version = RFC_VERSION;
525             rfc.reserved = 0;
526             rfc.len = htons(towrite + 4 + 3); /* RFC1006 length */
527             rfc.hlen = 2;
528             rfc.code = TPDU_CODE_DATA;
529             if (flags & T_MORE || towrite + writ < nbytes)
530                 eot = 0;
531             else
532                 eot = 1;
533             rfc.suffix[0] = eot << 7; /* DATA EOT marker */
534             if (cnt->togo < 0)
535                 head_offset = 7 + cnt->togo;
536             else
537                 head_offset = 0;
538             vec[0].iov_base = (caddr_t) (char*)&rfc + head_offset;
539             vec[0].iov_len = 7 - head_offset;
540             vec[1].iov_base = (caddr_t) buf + writ;
541             vec[1].iov_len = towrite;
542             TRC(fprintf(stderr, "  sending beg of block (%d+%d)\n",
543                 7 - head_offset, towrite));
544             if ((res = writev(fd, vec, 2)) < 0)
545             {
546                 TRC(fprintf(stderr, "  write returned -1\n"));
547                 /* thwarted by flow control */
548                 if (errno == EAGAIN)
549                 {
550                     if (writ)
551                         return writ;
552                     else
553                     {
554                         t_errno = TFLOW;
555                         return -1;
556                     }
557                 }
558                 else
559                     t_errno = TSYSERR;
560                 return -1;
561             }
562             /* somewhat thwarted */
563             else if (res < towrite + 7 - head_offset)
564             {
565                 /*
566                  * Write came up short. We assume that this is a flow-
567                  * control thing, and return immediately. Maybe it'd
568                  * be better to take another loop, and generate an
569                  * actual EAGAIN from write?
570                  */
571                 TRC(fprintf(stderr, "  write returned %d\n", res));
572                 if (res < 7 - head_offset) /* we didn't send a full header */
573                 {
574                     cnt->togo = -(7 - head_offset - res);
575                     t_errno = TFLOW;
576                     return -1;
577                 }
578                 else if ((res -= 7 - head_offset) < towrite) /* not all data */
579                 {
580                     cnt->togo = towrite - res;
581                     return nbytes - (writ + res);
582                 }
583             }
584             else /* whew... nonblocking I/O is hard work */
585             {
586                 cnt->togo = 0;
587                 writ += res - (7 - head_offset);
588             }
589         }
590     }
591     while (writ < nbytes);
592     TRC(fprintf(stderr, "  finishing with %d written\n", nbytes));
593     return nbytes;
594 }
595
596 int _t_rcv(int fd, char *buf, unsigned nbytes, int *flags)
597 {
598     struct rfct_control *cnt = control[fd];
599     struct tpdu_data_header dhead;
600     int res, got = 0;
601     struct iovec vec[2];
602     int toget;
603
604     TRC(fprintf(stderr, "T_RCV [nbytes=%d, flags=0x%x]\n", nbytes, *flags));
605     if (!cnt || cnt->state != T_DATAXFER)
606     {
607         t_errno = TOUTSTATE;
608         return -1;
609     }
610     *flags = 0;
611     do /* loop until we have a full TSDU or an error */
612     {
613         if (!cnt->event)
614             if (t_look_wait(fd, 0) <= 0)
615                 return -1;
616         if (cnt->event != T_DATA)
617         {
618             t_errno = TLOOK;
619             return -1;
620         }
621         if (cnt->hlen)  /* beginning of block */
622         {
623             TRC(fprintf(stderr, "  Beginning of TPDU\n"));
624             if (cnt->hlen > 2)
625             {
626                 TRC(fprintf(stderr, "We can't handle parameters to DATA\n"));
627                 t_errno = TSYSERR;
628                 return -1;
629             }
630             toget = cnt->pending;
631             if (toget > nbytes - got)
632                 toget = nbytes - got;
633             TRC(fprintf(stderr, "  toget=%d\n", toget));
634             vec[0].iov_base = (caddr_t) &dhead;
635             vec[0].iov_len = 1;
636             vec[1].iov_base = (caddr_t) buf + got;
637             vec[1].iov_len = toget;
638             if ((res = readv(fd, vec, 2)) < 0)
639             {
640                 TRC(perror("readv()"));
641                 if (errno == EAGAIN)
642                 {
643                     if (got)  /* we got some data in previous cycle */
644                         break;
645                     t_errno = TNODATA; /* no data */
646                     return -1;
647                 }
648                 t_errno = TSYSERR;
649                 return -1;
650             }
651             TRC(fprintf(stderr, "  readv() returned %d\n", res));
652             if (res == 0)
653             {
654                 t_errno = TLOOK;
655                 cnt->event = T_DISCONNECT;
656                 return -1;
657             }
658             got += res - 1;
659             cnt->eot_flag = (dhead.nr & DATA_EOT) >> 7;
660             cnt->hlen = 0;
661             cnt->pending -= got;
662             TRC(fprintf(stderr, "  Got total of %d octets, %d pending\n",
663                 got, cnt->pending));
664         }
665         else /* continuation */
666         {
667             TRC(fprintf(stderr, "  Reading middle of TPDU\n"));
668             toget = cnt->pending;
669             if (toget > nbytes - got)
670                 toget = nbytes - got;
671             TRC(fprintf(stderr, "  toget=%d\n", toget));
672             if ((res = read(fd, buf + got, toget)) < 0)
673             {
674                 TRC(perror("read()"));
675                 if (errno == EAGAIN)
676                 {
677                     if (got)  /* we got some data in previous cycle */
678                         break;
679                     t_errno = TNODATA; /* no data */
680                     return -1;
681                 }
682                 t_errno = TSYSERR;
683                 return -1;
684             }
685             TRC(fprintf(stderr, "  read() returned %d\n", res));
686             if (res == 0)
687             {
688                 t_errno = TLOOK;
689                 cnt->event = T_DISCONNECT;
690                 return -1;
691             }
692             got += res;
693             cnt->pending -= res;
694             TRC(fprintf(stderr, "  Got total of %d octets, %d pending\n",
695                 got, cnt->pending));
696         }
697         TRC(fprintf(stderr, "  bottom of loop: pending=%d, got=%d\n",
698             cnt->pending, got));
699     }
700     while (cnt->pending && got < nbytes);
701     *flags = cnt->pending || !cnt->eot_flag ? T_MORE : 0;
702     TRC(fprintf(stderr, "  flags=0x%x\n", *flags));
703     if (!cnt->pending)
704         cnt->event = 0;
705     TRC(fprintf(stderr, "  Return value: %d\n", got));
706     memset(buf + got, 0, 10);
707     return got;
708 }
709
710 int t_rcv(int fd, char *buf, unsigned nbytes, int *flags)
711 {
712     int res;
713     int total = 0;
714
715     do
716     {
717         if ((res = _t_rcv(fd, buf, nbytes, flags)) <= 0)
718             return res;
719         buf += res;
720         nbytes -= res;
721         total += res;
722     }
723     while (*flags & T_MORE && nbytes > 0);
724     return total;
725 }
726
727 int t_close(int fd)
728 {
729     struct rfct_control *cnt = control[fd];
730
731     TRC(fprintf(stderr, "T_CLOSE\n"));
732     if (!cnt || cnt->state == T_UNINIT)
733     {
734         TRC(fprintf(stderr, "Trying to close a bad fd.\n"));
735         t_errno = TBADF;
736         return -1;
737     }
738     free(cnt);
739     return close(fd);
740 }
741
742 /*
743  * This isn't right, obviously.
744  */
745 int t_error(char *errmsg)
746 {
747     TRC(fprintf(stderr, "T_ERROR\n"));
748     fprintf(stderr, "t_error(t_errno=%d):", t_errno);
749     perror(errmsg);
750     return 0;
751 }    
752
753 /*
754  * Put a select in here!!
755  */
756 static int t_look_wait(int fd, int wait)
757 {
758     struct rfct_control *cnt = control[fd];
759     struct rfc_header head;
760     int res;
761
762     TRC(fprintf(stderr, "T_LOOK\n"));
763     if (!cnt || cnt->state == T_UNINIT)
764     {
765         t_errno = TBADF;
766         return -1;
767     }
768     if (cnt->event)
769         return cnt->event;
770     if (cnt->state == T_IDLE && cnt->tmpfd < 0)
771         return T_LISTEN; /* the only possible type of event */
772     if ((res = read_n(fd, (char*) &head, 6)) < 0)
773         return -1;
774     if (head.version != RFC_VERSION)
775     {
776         TRC(fprintf(stderr, "Got bad RFC1006 version in t_look: %d.\n",
777             head.version));
778         t_errno = TSYSERR; /* should signal protocol error, somehow ## */
779         return -1;
780     }
781     cnt->curcode = head.code;
782     cnt->hlen = head.hlen - 1; /* length of header suffix */
783     cnt->pending = ntohs(head.len) - 6 - cnt->hlen;
784     TRC(fprintf(stderr, "t_look: len=%d, code=0x%2.2x, hlen=%d.\n",
785         cnt->pending + 6, cnt->curcode, cnt->hlen));
786     switch (cnt->curcode)
787     {
788         case TPDU_CODE_CREQ: cnt->event = T_LISTEN; break;
789         case TPDU_CODE_CCON: cnt->event = T_CONNECT; break;
790         case TPDU_CODE_DATA: cnt->event = T_DATA; break;
791         case TPDU_CODE_DREQ: cnt->event = T_DISCONNECT; break; 
792         default:
793             TRC(fprintf(stderr, "t_look: Bad package: 0x%2.2x.\n", cnt->curcode));
794             t_errno = TSYSERR;  /* protocol error */
795             return -1;
796     }
797     return cnt->event;
798 }    
799
800 int t_look(int fd)
801 {
802     return t_look_wait(fd, 0);
803 }
804
805 /*
806  * If the user doesn't provide a NSAP, and qlen > 0, we'll do a default
807  * server bind. If qlen==0, we won't bind at all - connect will do that
808  * for us.
809  */
810 int t_bind(int fd, struct t_bind *req, struct t_bind *ret)
811 {
812     struct rfct_control *cnt = control[fd];
813     struct sockaddr_in addr;
814     char *p;
815     int got_addr = 0;
816
817     TRC(fprintf(stderr, "T_BIND\n"));
818     if (!cnt || cnt->state != T_UNBND)
819     {
820         TRC(fprintf(stderr, "Bad state\n"));
821         t_errno = TOUTSTATE;
822         return -1;
823     }
824     cnt->ltsel_len = 0;
825     if (req)
826     {
827         cnt->qlen = req->qlen < MAX_QLEN ? req->qlen : MAX_QLEN;
828         if (req->addr.len)
829         {
830             p = req->addr.buf;
831             if (*p > TSEL_MAXLEN)
832             {
833                 TRC(fprintf(stderr, "Tsel too large.\n"));
834                 t_errno = TBADADDR;
835                 return -1;
836             }
837             cnt->ltsel_len = *p;
838             if (cnt->ltsel_len)
839                 memcpy(cnt->ltsel, p + 1, cnt->ltsel_len);
840             p += cnt->ltsel_len + 1;
841             if (*p < sizeof(addr))
842             {
843                 TRC(fprintf(stderr, "W: No NSAP provided for local bind\n"));
844             }
845             else
846             {
847                 memcpy(&addr, p + 1, sizeof(addr));
848                 got_addr = 1;
849             }
850         }
851     }
852     else
853         cnt->qlen = 0;
854     if (!got_addr) /* user didn't give an address - local bind */
855     {
856         addr.sin_family = AF_INET;
857         addr.sin_addr.s_addr = INADDR_ANY;
858         if (cnt->qlen)
859             addr.sin_port = htons(RFC_DEFAULT_PORT);
860         else /* we'll leave binding to connect - just set dummy */
861             addr.sin_port = 0; /* dummy for ret */
862     }
863     if (cnt->qlen && bind(fd, (struct sockaddr *) &addr,
864          sizeof(addr)) < 0 )
865     {
866         TRC(perror("bind()"));
867         t_errno = TSYSERR; /* this should be refined */
868         return -1;
869     }
870     if (cnt->qlen)
871     {
872         if (listen(fd, cnt->qlen) < 0)
873         {
874             t_errno = TSYSERR;
875             return -1;
876         }
877         cnt->listen = 1;
878         TRC(fprintf(stderr, "  listen OK\n"));
879     }
880     cnt->state = T_IDLE;
881     /* All right! Now let's give something back, if our user wants it */
882     if (ret)
883     {
884         ret->qlen = cnt->qlen;
885         if (ret->addr.maxlen < (ret->addr.len = cnt->ltsel_len + 2 +
886             sizeof(addr)))
887         {
888             /* No space - but we're still bound */
889             t_errno = TBUFOVFLW;
890             ret->addr.len = 0;
891             return -1;
892         }
893         p = ret->addr.buf;
894         *(p++) = cnt->ltsel_len;
895         if (cnt->ltsel_len)
896             memcpy(p, cnt->ltsel, cnt->ltsel_len);
897         p += cnt->ltsel_len;
898         *(p++) = sizeof(addr);
899         memcpy(p, &addr, sizeof(addr));
900     }
901     return 0;
902 }    
903
904 /*
905  * need to consult RFC1006 on these. I think they just map to close()...
906  */
907 int t_snddis(int fd, struct t_call *call)
908 {
909     TRC(fprintf(stderr, "T_SNDDIS\n"));
910     return 0;
911 }
912
913 int t_rcvdis(int fd, struct t_discon *discon)
914 {
915     struct rfct_control *cnt = control[fd];
916     struct tpdu_disconnect_header chead;
917     char udata[64], buf[256], *p;
918
919     TRC(fprintf(stderr, "T_RCVDIS\n"));
920     if (!cnt) 
921     {
922         TRC(fprintf(stderr, "TOUTSTATE\n"));
923         t_errno = TOUTSTATE;
924         return -1;
925     }
926     if (!cnt->event)
927         if (t_look_wait(fd, 1) <= 0)
928             return -1;
929     if (cnt->event != T_DISCONNECT)
930     {
931         t_errno = TLOOK;
932         return -1;
933     }
934     /* read the rest of the DR TPDU */
935     if (read_n(fd, buf, cnt->hlen) <= 0)
936         return -1;
937     memcpy(&chead, buf, 5);
938     cnt->hlen -= 5;
939     for (p = buf + 5; cnt->hlen > 0;)
940     {
941         switch ((unsigned char)*p)
942         {
943             default:
944                 TRC(fprintf(stderr, "Inoring RD parameter: %d\n",
945                     (unsigned char)*p));
946             /* we silently ignore anything else */
947         }
948         p++;
949         cnt->hlen -= (unsigned char) *p + 2;
950         p += (unsigned char) *p + 1;
951     }
952     if (cnt->pending)
953     {
954         if (read_n(fd, udata, cnt->pending) < cnt->pending)
955         {
956             TRC(fprintf(stderr, "Unable to read user data\n"));
957             t_errno = TSYSERR;
958             return -1;
959         }
960     }
961     cnt->state = T_IDLE;  /* should we close transport? */
962     cnt->event = 0;
963     if (discon)
964     {
965         discon->sequence = -1; /* TOFIX */
966         discon->reason = chead.reason;
967         TRC(fprintf(stderr, "Diconnect reason %d\n", chead.reason));
968         if (cnt->pending > discon->udata.maxlen)
969         {
970             t_errno = TBUFOVFLW;
971             return -1;
972         }
973         if (cnt->pending)
974         {
975             memcpy(discon->udata.buf, udata, cnt->pending);
976             udata[cnt->pending] = '\0';
977             TRC(fprintf(stderr, "Discon udata: '%s'\n", udata));
978         }
979         discon->udata.len = cnt->pending;
980     }
981     return 0;
982 }    
983
984 /*
985  * fix memory management, you bad Sebastian!
986  */
987 int t_free(char *ptr, int struct_type)
988 {
989     TRC(fprintf(stderr, "T_FREE\n"));
990     free(ptr);
991     return 0;
992 }    
993
994 char *t_alloc(int fd, int struct_type, int fields)
995 {
996     char *r = malloc(1024);
997     if (!r)
998         return 0;
999     TRC(fprintf(stderr, "T_ALLOC\n"));
1000     memset(r, 0, 1024);
1001     return r;
1002 }
1003
1004 /*
1005  * this is required for t_listen... if a system doesn't support dup2(), we're
1006  * in trouble: We might not be able to do nonblocking listen. Time will tell.
1007  */
1008 static int switch_fds(int fd1, int fd2)
1009 {
1010     int tmp;
1011     struct rfct_control *tmpc;
1012     
1013     TRC(fprintf(stderr, "Switching fds %d <--> %d\n", fd1, fd2));
1014     if ((tmp = dup(fd1)) < 0 ||
1015         dup2(fd2, fd1) < 0 ||
1016         dup2(tmp, fd2) < 0 ||
1017         close(tmp) < 0)
1018             return -1;
1019     tmpc = control[fd1];
1020     control[fd1] = control[fd2];
1021     control[fd2] = tmpc;
1022     return 0;
1023 }
1024
1025 static int rcvconreq(int fd, struct t_call *call)
1026 {
1027     struct rfct_control *cnt = control[fd];
1028     struct rfct_control *new = control[cnt->tmpfd];
1029     struct tpdu_connect_header chead;
1030     char buf[100];
1031     char *p, *addrp = 0;
1032     struct sockaddr_in addr;
1033     int len = sizeof(struct sockaddr_in);
1034     int qslot;
1035
1036     TRC(fprintf(stderr, "RCVCONRES\n"));
1037     if (!call)
1038     {
1039         t_errno = TSYSERR;
1040         return -1;
1041     }
1042     for (qslot = 0; qslot < cnt->qlen; qslot++)
1043         if (cnt->oci[qslot] < 0)
1044             break;
1045     if (qslot == cnt->qlen) /* no free slots - shouldn't happen here */
1046     {
1047         t_errno = TBADF;
1048         return -1;
1049     }
1050     /* read the rest of the CREQ TPDU */
1051     if (read_n(cnt->tmpfd, buf, new->hlen) <= 0)
1052         return -1;
1053     memcpy(&chead, buf, 5);
1054     if (chead.class != 0)
1055     {
1056         TRC(fprintf(stderr, "Expected TP0, got %d\n", (int)chead.class));
1057         t_errno = TSYSERR;
1058         return -1;
1059     }
1060     memcpy(new->rref, chead.src_ref, 2); /* we'll echo this back at her */
1061     new->hlen -= 5;
1062     if (call && call->addr.maxlen)
1063         *(addrp = call->addr.buf) = 0;
1064     for (p = buf + 5; new->hlen > 0;)
1065     {
1066         switch ((unsigned char)*p)
1067         {
1068             case TPDU_PARM_TSIZE:
1069                 new->tsize = 1 << *(p + 2); /* we go with their max */
1070                 break;
1071             case TPDU_PARM_CLDID: break; /* ignore */
1072             case TPDU_PARM_CLGID:
1073                 if (addrp)
1074                 {
1075                     if (*(p + 1) > TSEL_MAXLEN)
1076                     {
1077                         TRC(fprintf(stderr, "Called TSEL too long.\n"));
1078                         t_errno = TSYSERR; /* Wrong.. ## */
1079                         return -1;
1080                     }
1081                     *addrp = *(p + 1); /* length */
1082                     memcpy(addrp + 1, p + 2, *(p + 1)); /* remote TSEL */
1083                     addrp += *(p + 1); /* move past TSEL */
1084                 }
1085                 break;
1086             /* we silently ignore preferred TPDU size and others */
1087         }
1088         p++;
1089         new->hlen -= (unsigned char) *p + 2;
1090         p += (unsigned char) *p + 1;
1091     }
1092     if (addrp)
1093     {
1094         addrp++;
1095         if (getpeername(cnt->tmpfd, (struct sockaddr*) &addr, &len) < 0)
1096         {
1097             TRC(perror("getpeername()"));
1098             t_errno = TSYSERR;
1099             return -1;
1100         }
1101         *(addrp++) = sizeof(struct sockaddr_in);
1102         memcpy(addrp, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));
1103     }
1104     new->event = 0;
1105     cnt->event = 0;
1106     cnt->oci[qslot] = cnt->tmpfd; /* move the new semi-connection to oci */
1107     call->sequence = qslot;
1108     cnt->tmpfd = -1;
1109     cnt->state = T_INCON;
1110     return 0;
1111 }
1112
1113 /*
1114  * This construction is tricky in async mode: listen calls accept, which
1115  * generates a new fd for the association. Call is supposed to return
1116  * immediately if there is no CR on the line (which is highly likely),
1117  * and t-user will then try to use select() on the wrong socket, waiting for
1118  * the transport level package. 
1119  *
1120  * Compared to the upper-level u_* routines, we have one major handicap
1121  * and one major benefit: We *have* to handle two different endpoints
1122  * after t_listen (which includes sockets accept); and we have access
1123  * to dup2().
1124  *
1125  * If the endpoint is configured for nonblocking I/O, and listen is not
1126  * able to immediately acquire the CR, the two fds  are switched, so that
1127  * subsequent selects take place on the data socket, rather than the
1128  * listener socket.
1129  *
1130  * At any rate, the data socket is saved, so that it can later be given
1131  * to the user in t_accept().
1132  */
1133 int t_listen(int fd, struct t_call *call)
1134 {
1135     struct rfct_control *cnt = control[fd], *new;
1136     struct sockaddr_in addr;
1137     int addrlen = sizeof(struct sockaddr_in);
1138     int tmpfd_is_new = 0; /* we've just accept()ed a connection */
1139     int event, i;
1140
1141     TRC(fprintf(stderr, "T_LISTEN\n"));
1142     if (!cnt || cnt->state != T_IDLE)
1143     {
1144         TRC(fprintf(stderr, "T_listen expects state==T_IDLE (wrong?)\n"));
1145         t_errno = TOUTSTATE;
1146         return -1;
1147     }
1148     if (!cnt->qlen)
1149     {
1150         t_errno = TBADQLEN;
1151         return -1;
1152     }
1153     for (i = 0; i < cnt->qlen; i++)
1154         if (cnt->oci[i] < 0)
1155             break;
1156     if (i == cnt->qlen) /* no slots in queue */
1157     {
1158         TRC(fprintf(stderr, "No more space in queue\n"));
1159         t_errno = TBADF; /* what would be more correct? */
1160         return -1;
1161     }
1162     if (cnt->tmpfd < 0)
1163     {
1164         TRC(fprintf(stderr, "Accept..."));
1165         if ((cnt->tmpfd = accept(fd, (struct sockaddr*) &addr, &addrlen)) < 0)
1166         {
1167             if (errno == EWOULDBLOCK)
1168             {
1169                 t_errno = TNODATA;
1170                 TRC(fprintf(stderr, "Accept returned WOULDBLOCK\n"));
1171             }
1172             else
1173             {
1174                 TRC(fprintf(stderr, "accept failed\n"));
1175                 t_errno = TSYSERR;
1176             }
1177             return -1;
1178         }
1179 #ifdef NONBLOCKING_OSI
1180         if ((cnt->flags & O_NONBLOCK) && fcntl(cnt->tmpfd, F_SETFL,
1181             O_NONBLOCK) < 0)
1182         {
1183             t_errno = TSYSERR;
1184             return -1;
1185         }
1186 #endif
1187         tmpfd_is_new = 1;
1188         TRC(fprintf(stderr, "Accept OK\n"));
1189         if (!(new = control[cnt->tmpfd] = malloc(sizeof(*new))))
1190         {
1191             TRC(perror("malloc()"));
1192             t_errno = TSYSERR;
1193             return -1;
1194         }
1195         new->togo = 0;
1196         new->pending = 0;
1197         new->state = T_IDLE;
1198         new->event = 0;
1199         new->listen = 0;
1200         new->qlen = cnt->qlen;
1201         new->flags = cnt->flags;
1202         new->tmpfd = cnt->tmpfd;
1203         new->ltsel_len = 0;
1204         for (i = 0; i < MAX_QLEN; i++)
1205             new->oci[i] = -1;
1206     }
1207     /* we got a network connection. Now try to read transport CREQ TPDU */
1208     if ((event = t_look_wait(tmpfd_is_new ? cnt->tmpfd : fd, 1)) < 0)
1209     {
1210         if (t_errno == TNODATA)
1211         {
1212             if (tmpfd_is_new)
1213             {
1214                 /*
1215                  * We give the user something to select on for the incoming
1216                  * CR. Switch the new association with the listener socket.
1217                  */
1218                 TRC(fprintf(stderr, "Switching FDs\n"));
1219                 if (switch_fds(cnt->tmpfd, fd) < 0)
1220                 {
1221                     t_errno = TSYSERR;
1222                     return -1;
1223                 }
1224             }
1225             return -1;
1226         }
1227         else
1228         {
1229             t_close(cnt->tmpfd);
1230             cnt->tmpfd = -1;
1231         }
1232         return -1; /* t_look & t_read hopefully set up the right errcodes */
1233     }
1234     else
1235     {
1236         /* We got something! */
1237         if (event != T_LISTEN)
1238         {
1239             TRC(fprintf(stderr, "Expected T_LISTEN\n"));
1240             t_errno = TLOOK;
1241             return -1;
1242         }
1243         /*
1244          * switch back the fds, if necessary */
1245         if (!tmpfd_is_new && switch_fds(fd, cnt->tmpfd) < 0)
1246         {
1247             t_errno = TSYSERR;
1248             return -1;
1249         }
1250         if (rcvconreq(fd, call) < 0)
1251         {
1252             t_close(cnt->tmpfd);
1253             cnt->tmpfd = -1;
1254         }
1255         return 0;
1256     }
1257 }    
1258
1259 /*
1260  * There's no clean mapping of this onto the socket interface. If someone
1261  * wants it, we could fake it by first closing the socket, and then
1262  * opening a new one and doing a dup2. That should be functionally
1263  * equivalent(?).
1264  */
1265 int t_unbind(int fd)
1266 {
1267     fprintf(stderr, "T_UNBIND [not supported by transport implementation]\n");
1268     t_errno = TNOTSUPPORT;
1269     return -1;
1270 }    
1271
1272 int t_accept(int fd, int resfd, struct t_call *call)
1273 {
1274     struct rfct_control *listener = control[fd]; /* listener handle */
1275     struct rfct_control *new; /* new, semi-complete association */
1276     struct rfct_control *res; /* resulting association handle */
1277     struct iovec vec[3]; /* RFC1006 header + T-header + parm */
1278     struct rfc_header rfc;
1279     struct tpdu_connect_header tpdu;
1280     unsigned char parm[3 + TSEL_MAXLEN + 2];
1281     int i, newfd;
1282     
1283     TRC(fprintf(stderr, "T_ACCEPT\n"));
1284     if (!listener || listener->state != T_INCON)
1285     {
1286         TRC(fprintf(stderr, "TOUTSTATE\n"));
1287         t_errno = TOUTSTATE;
1288         return -1;
1289     }
1290     /* Get the semi-connection */
1291     if (call->sequence >= listener->qlen || listener->oci[call->sequence] < 0)
1292     {
1293         TRC(fprintf(stderr, "TBADSEQ\n"));
1294         t_errno = TBADSEQ;
1295         return -1;
1296     }
1297     new = control[(newfd = listener->oci[call->sequence])];
1298     listener->oci[call->sequence] = -1;
1299     res = control[resfd];
1300     if (!res)
1301     {
1302         t_errno = TBADF;
1303         return -1;
1304     }
1305     if (res != listener) /* move the new connection */
1306     {
1307         TRC(fprintf(stderr, "  Moving to new fd (%d)\n", resfd));
1308         if (res->state != T_IDLE || res->qlen)
1309         {
1310             TRC(fprintf(stderr, "Trying to move new assc. to bad fd.\n"));
1311             t_errno = TBADF;
1312             return -1;
1313         }
1314         dup2(newfd, resfd); /* closes resfd */
1315         close(newfd);
1316         control[resfd] = new;
1317         /* transfer local bindings from res */
1318         if (res->ltsel_len)
1319             memcpy(control[resfd]->ltsel, res->ltsel, res->ltsel_len);
1320         control[resfd]->ltsel_len = res->ltsel_len;
1321         free(res);
1322         res = control[resfd];
1323         listener->event = 0;
1324         listener->state = T_IDLE;
1325     }
1326     else /* lose our listener */
1327     {
1328         TRC(fprintf(stderr, "  Moving to listener fd\n"));
1329         for (i = 0; i < listener->qlen; i++)
1330             if (listener->oci[i] >= 0)
1331             {
1332                 TRC(fprintf(stderr, "Still conn indications on listener\n"));
1333                 t_errno = TBADF;
1334                 return -1;
1335             }
1336         dup2(newfd, fd);
1337         close(newfd);
1338         control[fd] = new;
1339         if (listener->ltsel_len)
1340             memcpy(control[resfd]->ltsel, listener->ltsel, listener->ltsel_len);
1341         control[resfd]->ltsel_len = listener->ltsel_len;
1342         free(listener);
1343         res = control[resfd];
1344     }
1345     rfc.version = RFC_VERSION;
1346     rfc.reserved = 0;
1347     rfc.code = TPDU_CODE_CCON;
1348
1349     memcpy(tpdu.src_ref, "AA", 2);
1350     memcpy(tpdu.dst_ref, res->rref, 2); /* echo back at 'em */
1351     tpdu.class = 0;
1352
1353     /* grant them their TPDU size */
1354     parm[0] = TPDU_PARM_TSIZE;
1355     parm[1] = 1;
1356     for (i = 7; i <= 11 && (1 << i) < res->tsize; i++) ; /* encode TPDU size */
1357     parm[2] = i;
1358     /* give our TSEL. ## Must we echo theirs, if given? check spec */
1359     /* I think it was ok to give an empty TSEL. Does it have semantic sig? */
1360     parm[3] = TPDU_PARM_CLDID;
1361     parm[4] = res->ltsel_len; 
1362     if (res->ltsel_len)
1363         memcpy(parm + 5, res->ltsel, res->ltsel_len);
1364
1365     rfc.len =  htons(4 + 7 + 3 + 2 + res->ltsel_len);
1366     rfc.hlen = 6 + 3 + 2 + res->ltsel_len;
1367     vec[0].iov_base = (caddr_t) &rfc;
1368     vec[0].iov_len = 6;
1369     vec[1].iov_base = (caddr_t) &tpdu;
1370     vec[1].iov_len = 5;
1371     vec[2].iov_base = (caddr_t) parm;
1372     vec[2].iov_len = 3 + 2 + res->ltsel_len;
1373     if (writev(resfd, vec, 3) < 4 + 7 + 3 + (2 + res->ltsel_len))
1374     {
1375         TRC(fprintf(stderr, "writev came up short. Aborting connect\n"));
1376         t_errno = TSYSERR;
1377         return -1;
1378     }
1379     res->state = T_DATAXFER;
1380     res->event = 0;
1381     return 0;
1382 }    
1383
1384 int t_getstate(int fd)
1385 {
1386     TRC(fprintf(stderr, "T_GETSTATE\n"));
1387     return control[fd] ? control[fd]->state : T_UNINIT;
1388 }