1 /***************************************************************************
2 begin : Tue Oct 02 2002
3 copyright : (C) 2002 by Martin Preuss
4 email : martin@libchipcard.de
5
6 ***************************************************************************
7 * *
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU Lesser General Public *
10 * License as published by the Free Software Foundation; either *
11 * version 2.1 of the License, or (at your option) any later version. *
12 * *
13 * This library is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16 * Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public *
19 * License along with this library; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21 * MA 02111-1307 USA *
22 * *
23 ***************************************************************************/
24
25
26 #ifdef HAVE_CONFIG_H
27 # include <config.h>
28 #endif
29
30
31
32 #include "inetsocket_p.h"
33 #include "inetaddr_p.h"
34 #include "errorstring.h"
35 #include <gwenhywfar/misc.h>
36 #include <gwenhywfar/debug.h>
37
38 #include <errno.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <sys/time.h>
44
45
46
47 GWEN_LIST_FUNCTIONS(GWEN_SOCKET, GWEN_Socket)
48 GWEN_LIST2_FUNCTIONS(GWEN_SOCKET, GWEN_Socket)
49
50
51 static int gwen_socket_is_initialized=0;
52
53
54
GWEN_Socket_ModuleInit(void)55 int GWEN_Socket_ModuleInit(void)
56 {
57 WORD wVersionRequested;
58 WSADATA wsaData;
59
60 if (!gwen_socket_is_initialized) {
61 int rv;
62
63 /* setup WINSOCK (request version 1.1) */
64 wVersionRequested=MAKEWORD(1, 1);
65 rv=WSAStartup(wVersionRequested, &wsaData);
66 if (rv) {
67 DBG_INFO(GWEN_LOGDOMAIN, "Error on WSAStartup");
68 return rv;
69 }
70 /* check if the version returned is that we requested */
71 if (LOBYTE(wsaData.wVersion)!=1 ||
72 HIBYTE(wsaData.wVersion)!=1) {
73 WSACleanup();
74 return GWEN_ERROR_STARTUP;
75 }
76
77 gwen_socket_is_initialized=1;
78 }
79 return 0;
80 }
81
82
83
GWEN_Socket_ModuleFini(void)84 int GWEN_Socket_ModuleFini(void)
85 {
86 if (gwen_socket_is_initialized) {
87
88 WSACleanup();
89
90 gwen_socket_is_initialized=0;
91 }
92 return 0;
93 }
94
95
96
GWEN_SocketSet_Clear(GWEN_SOCKETSET * ssp)97 int GWEN_SocketSet_Clear(GWEN_SOCKETSET *ssp)
98 {
99 assert(ssp);
100 FD_ZERO(&(ssp->set));
101 ssp->highest=0;
102 return 0;
103 }
104
105
106
GWEN_SocketSet_new(void)107 GWEN_SOCKETSET *GWEN_SocketSet_new(void)
108 {
109 GWEN_SOCKETSET *ssp;
110
111 GWEN_NEW_OBJECT(GWEN_SOCKETSET, ssp);
112 FD_ZERO(&(ssp->set));
113 return ssp;
114 }
115
116
117
GWEN_Socket_fromFile(int fd)118 GWEN_SOCKET *GWEN_Socket_fromFile(int fd)
119 {
120 DBG_ERROR(GWEN_LOGDOMAIN,
121 "No file sockets available for this system");
122 return 0;
123 }
124
125
126
GWEN_SocketSet_free(GWEN_SOCKETSET * ssp)127 void GWEN_SocketSet_free(GWEN_SOCKETSET *ssp)
128 {
129 if (ssp) {
130 FD_ZERO(&(ssp->set));
131 GWEN_FREE_OBJECT(ssp);
132 }
133 }
134
135
136
GWEN_SocketSet_AddSocket(GWEN_SOCKETSET * ssp,const GWEN_SOCKET * sp)137 int GWEN_SocketSet_AddSocket(GWEN_SOCKETSET *ssp,
138 const GWEN_SOCKET *sp)
139 {
140 assert(ssp);
141 assert(sp);
142 if (sp->socket==-1) {
143 DBG_INFO(GWEN_LOGDOMAIN, "Socket is not connected, can not add");
144 return GWEN_ERROR_NOT_OPEN;
145 }
146 ssp->highest=(ssp->highest<sp->socket)?sp->socket:ssp->highest;
147 FD_SET(sp->socket, &(ssp->set));
148 ssp->count++;
149 return 0;
150 }
151
152
153
GWEN_SocketSet_RemoveSocket(GWEN_SOCKETSET * ssp,const GWEN_SOCKET * sp)154 int GWEN_SocketSet_RemoveSocket(GWEN_SOCKETSET *ssp,
155 const GWEN_SOCKET *sp)
156 {
157 assert(ssp);
158 assert(sp);
159 ssp->highest=(ssp->highest<sp->socket)?sp->socket:ssp->highest;
160 FD_CLR(sp->socket, &(ssp->set));
161 ssp->count--;
162 return 0;
163 }
164
165
166
GWEN_SocketSet_HasSocket(GWEN_SOCKETSET * ssp,const GWEN_SOCKET * sp)167 int GWEN_SocketSet_HasSocket(GWEN_SOCKETSET *ssp,
168 const GWEN_SOCKET *sp)
169 {
170 assert(ssp);
171 assert(sp);
172 return FD_ISSET(sp->socket, &(ssp->set));
173 }
174
175
176
GWEN_SocketSet_GetSocketCount(GWEN_SOCKETSET * ssp)177 int GWEN_SocketSet_GetSocketCount(GWEN_SOCKETSET *ssp)
178 {
179 assert(ssp);
180 return ssp->count;
181 }
182
183
184
185
186
GWEN_Socket_new(GWEN_SOCKETTYPE socketType)187 GWEN_SOCKET *GWEN_Socket_new(GWEN_SOCKETTYPE socketType)
188 {
189 GWEN_SOCKET *sp;
190
191 GWEN_NEW_OBJECT(GWEN_SOCKET, sp);
192 GWEN_LIST_INIT(GWEN_SOCKET, sp);
193 sp->type=socketType;
194 return sp;
195 }
196
197
198
GWEN_Socket_free(GWEN_SOCKET * sp)199 void GWEN_Socket_free(GWEN_SOCKET *sp)
200 {
201 if (sp) {
202 GWEN_LIST_FINI(GWEN_SOCKET, sp);
203 GWEN_FREE_OBJECT(sp);
204 }
205 }
206
207
208
GWEN_Socket_Open(GWEN_SOCKET * sp)209 int GWEN_Socket_Open(GWEN_SOCKET *sp)
210 {
211 int s;
212
213 assert(sp);
214 switch (sp->type) {
215 case GWEN_SocketTypeTCP:
216 #ifdef PF_INET
217 s=socket(PF_INET, SOCK_STREAM, 0);
218 #else
219 s=socket(AF_INET, SOCK_STREAM, 0);
220 #endif
221 if (s==-1)
222 return WSAGetLastError();
223 sp->socket=s;
224 break;
225
226 case GWEN_SocketTypeUDP:
227 #ifdef PF_INET
228 s=socket(PF_INET, SOCK_DGRAM, 0);
229 #else
230 s=socket(AF_INET, SOCK_DGRAM, 0);
231 #endif
232 if (s==-1)
233 return WSAGetLastError();
234 sp->socket=s;
235 break;
236
237 case GWEN_SocketTypeUnix:
238 DBG_ERROR(GWEN_LOGDOMAIN, "No unix domain sockets available for this system");
239 return GWEN_ERROR_BAD_ADDRESS_FAMILY;
240 break;
241
242 default:
243 return GWEN_ERROR_BAD_SOCKETTYPE;
244 } /* switch */
245
246 return 0;
247 }
248
249
250
GWEN_Socket_Connect(GWEN_SOCKET * sp,const GWEN_INETADDRESS * addr)251 int GWEN_Socket_Connect(GWEN_SOCKET *sp,
252 const GWEN_INETADDRESS *addr)
253 {
254 assert(sp);
255 if (connect(sp->socket,
256 addr->address,
257 addr->size)) {
258 if (WSAGetLastError()!=WSAEINPROGRESS &&
259 WSAGetLastError()!=WSAEWOULDBLOCK) {
260 DBG_INFO(GWEN_LOGDOMAIN, "Error %d (%s)",
261 WSAGetLastError(),
262 GWEN_ErrorString_Windows(WSAGetLastError()));
263 return WSAGetLastError();
264 }
265 else
266 return GWEN_ERROR_IN_PROGRESS;
267 }
268 return 0;
269 }
270
271
272
GWEN_Socket_Close(GWEN_SOCKET * sp)273 int GWEN_Socket_Close(GWEN_SOCKET *sp)
274 {
275 int rv;
276
277 assert(sp);
278 if (sp->socket==-1)
279 return GWEN_ERROR_NOT_OPEN;
280
281 rv=closesocket(sp->socket);
282 sp->socket=-1;
283 if (rv==-1)
284 return WSAGetLastError();
285 return 0;
286 }
287
288
289
GWEN_Socket_Bind(GWEN_SOCKET * sp,const GWEN_INETADDRESS * addr)290 int GWEN_Socket_Bind(GWEN_SOCKET *sp,
291 const GWEN_INETADDRESS *addr)
292 {
293 assert(sp);
294 assert(addr);
295 if (bind(sp->socket,
296 addr->address,
297 addr->size))
298 return WSAGetLastError();
299 return 0;
300 }
301
302
303
GWEN_Socket_Listen(GWEN_SOCKET * sp,int backlog)304 int GWEN_Socket_Listen(GWEN_SOCKET *sp, int backlog)
305 {
306 assert(sp);
307 if (listen(sp->socket, backlog))
308 return WSAGetLastError();
309 return 0;
310 }
311
312
313
GWEN_Socket_Accept(GWEN_SOCKET * sp,GWEN_INETADDRESS ** newaddr,GWEN_SOCKET ** newsock)314 int GWEN_Socket_Accept(GWEN_SOCKET *sp,
315 GWEN_INETADDRESS **newaddr,
316 GWEN_SOCKET **newsock)
317 {
318 int addrlen;
319 GWEN_INETADDRESS *localAddr;
320 GWEN_SOCKET *localSocket;
321 GWEN_AddressFamily af;
322
323 assert(sp);
324 assert(newsock);
325 assert(newaddr);
326
327 switch (sp->type) {
328 case GWEN_SocketTypeTCP:
329 case GWEN_SocketTypeUDP:
330 af=GWEN_AddressFamilyIP;
331 break;
332 case GWEN_SocketTypeUnix:
333 af=GWEN_AddressFamilyUnix;
334 break;
335 default:
336 return GWEN_ERROR_BAD_SOCKETTYPE;
337 } /* switch */
338
339 localAddr=GWEN_InetAddr_new(af);
340 addrlen=localAddr->size;
341 localSocket=GWEN_Socket_new(sp->type);
342 localSocket->socket=accept(sp->socket,
343 localAddr->address,
344 &addrlen);
345 if (localSocket->socket==-1) {
346 GWEN_InetAddr_free(localAddr);
347 GWEN_Socket_free(localSocket);
348 if (WSAGetLastError()==WSAEWOULDBLOCK)
349 return GWEN_ERROR_TIMEOUT;
350 else
351 return WSAGetLastError();
352 }
353 localSocket->type=sp->type;
354 localAddr->size=addrlen;
355 *newaddr=localAddr;
356 *newsock=localSocket;
357 return 0;
358 }
359
360
361
GWEN_Socket_GetPeerAddr(GWEN_SOCKET * sp,GWEN_INETADDRESS ** newaddr)362 int GWEN_Socket_GetPeerAddr(GWEN_SOCKET *sp,
363 GWEN_INETADDRESS **newaddr)
364 {
365 int addrlen;
366 GWEN_INETADDRESS *localAddr;
367 GWEN_AddressFamily af;
368
369 assert(sp);
370 assert(newaddr);
371
372 switch (sp->type) {
373 case GWEN_SocketTypeTCP:
374 case GWEN_SocketTypeUDP:
375 af=GWEN_AddressFamilyIP;
376 break;
377 case GWEN_SocketTypeUnix:
378 af=GWEN_AddressFamilyUnix;
379 break;
380 default:
381 return GWEN_ERROR_BAD_SOCKETTYPE;
382 } /* switch */
383
384 localAddr=GWEN_InetAddr_new(af);
385 addrlen=localAddr->size;
386
387 if (getpeername(sp->socket,
388 localAddr->address, &addrlen)) {
389 GWEN_InetAddr_free(localAddr);
390 return WSAGetLastError();
391 }
392 localAddr->size=addrlen;
393 *newaddr=localAddr;
394 return 0;
395 }
396
397
398
GWEN_Socket_Select(GWEN_SOCKETSET * rs,GWEN_SOCKETSET * ws,GWEN_SOCKETSET * xs,int timeout)399 int GWEN_Socket_Select(GWEN_SOCKETSET *rs,
400 GWEN_SOCKETSET *ws,
401 GWEN_SOCKETSET *xs,
402 int timeout)
403 {
404 int h, h1, h2, h3;
405 fd_set *s1, *s2, *s3;
406 int rv;
407 struct timeval tv;
408
409 s1=s2=s3=0;
410 h1=h2=h3=0;
411
412 if (rs) {
413 h1=rs->highest;
414 s1=&rs->set;
415 }
416 if (ws) {
417 h2=ws->highest;
418 s2=&ws->set;
419 }
420 if (xs) {
421 h3=xs->highest;
422 s3=&xs->set;
423 }
424 h=(h1>h2)?h1:h2;
425 h=(h>h3)?h:h3;
426 if (timeout<0)
427 /* wait for ever */
428 rv=select(h+1, s1, s2, s3, 0);
429 else {
430 /* return immediately */
431 tv.tv_sec=0;
432 tv.tv_usec=timeout*1000;
433 rv=select(h+1, s1, s2, s3, &tv);
434 }
435 if (rv<0) {
436 /* error */
437 if (WSAGetLastError()==WSAEINTR)
438 return GWEN_ERROR_INTERRUPTED;
439 else
440 return WSAGetLastError();
441 }
442 if (rv==0)
443 /* timeout */
444 return GWEN_ERROR_TIMEOUT;
445 return 0;
446 }
447
448
449
GWEN_Socket_Read(GWEN_SOCKET * sp,char * buffer,int * bsize)450 int GWEN_Socket_Read(GWEN_SOCKET *sp,
451 char *buffer,
452 int *bsize)
453 {
454 int i;
455
456 assert(sp);
457 assert(buffer);
458 assert(bsize);
459 i=recv(sp->socket, buffer, *bsize, 0);
460 if (i<0) {
461 if (WSAGetLastError()==WSAEWOULDBLOCK)
462 return GWEN_ERROR_TIMEOUT;
463 else if (WSAGetLastError()==WSAEINTR)
464 return GWEN_ERROR_INTERRUPTED;
465 else
466 return WSAGetLastError();
467 }
468 *bsize=i;
469 return 0;
470 }
471
472
473
GWEN_Socket_Write(GWEN_SOCKET * sp,const char * buffer,int * bsize)474 int GWEN_Socket_Write(GWEN_SOCKET *sp,
475 const char *buffer,
476 int *bsize)
477 {
478 int i;
479
480 assert(sp);
481 assert(buffer);
482 assert(bsize);
483 #ifndef MSG_NOSIGNAL
484 i=send(sp->socket, buffer, *bsize, 0);
485 #else
486 i=send(sp->socket, buffer, *bsize, MSG_NOSIGNAL);
487 #endif
488 if (i<0) {
489 if (WSAGetLastError()==WSAEWOULDBLOCK)
490 return GWEN_ERROR_TIMEOUT;
491 else if (WSAGetLastError()==WSAEINTR)
492 return GWEN_ERROR_INTERRUPTED;
493 else
494 return WSAGetLastError();
495 }
496 *bsize=i;
497 return 0;
498 }
499
500
501
GWEN_Socket_ReadFrom(GWEN_SOCKET * sp,GWEN_INETADDRESS ** newaddr,char * buffer,int * bsize)502 int GWEN_Socket_ReadFrom(GWEN_SOCKET *sp,
503 GWEN_INETADDRESS **newaddr,
504 char *buffer,
505 int *bsize)
506 {
507 int addrlen;
508 int i;
509 GWEN_INETADDRESS *localAddr;
510 GWEN_AddressFamily af;
511
512 assert(sp);
513 assert(newaddr);
514 assert(buffer);
515 assert(bsize);
516
517 switch (sp->type) {
518 case GWEN_SocketTypeTCP:
519 case GWEN_SocketTypeUDP:
520 af=GWEN_AddressFamilyIP;
521 break;
522 case GWEN_SocketTypeUnix:
523 af=GWEN_AddressFamilyUnix;
524 break;
525 default:
526 return GWEN_ERROR_BAD_SOCKETTYPE;
527 } /* switch */
528
529 localAddr=GWEN_InetAddr_new(af);
530 addrlen=localAddr->size;
531
532 i=recvfrom(sp->socket,
533 buffer,
534 *bsize,
535 0,
536 localAddr->address,
537 &addrlen);
538 if (i<0) {
539 GWEN_InetAddr_free(localAddr);
540 if (WSAGetLastError()==WSAEWOULDBLOCK)
541 return GWEN_ERROR_TIMEOUT;
542 else if (WSAGetLastError()==WSAEINTR)
543 return GWEN_ERROR_INTERRUPTED;
544 else
545 return WSAGetLastError();
546 }
547 *bsize=i;
548 localAddr->size=addrlen;
549 *newaddr=localAddr;
550 return 0;
551 }
552
553
554
GWEN_Socket_WriteTo(GWEN_SOCKET * sp,const GWEN_INETADDRESS * addr,const char * buffer,int * bsize)555 int GWEN_Socket_WriteTo(GWEN_SOCKET *sp,
556 const GWEN_INETADDRESS *addr,
557 const char *buffer,
558 int *bsize)
559 {
560 int i;
561
562 assert(sp);
563 assert(addr);
564 assert(buffer);
565 assert(bsize);
566 i=sendto(sp->socket,
567 buffer,
568 *bsize,
569 #ifndef MSG_NOSIGNAL
570 0,
571 #else
572 MSG_NOSIGNAL,
573 #endif
574 addr->address,
575 addr->size);
576 if (i<0) {
577 if (WSAGetLastError()==WSAEWOULDBLOCK)
578 return GWEN_ERROR_TIMEOUT;
579 else if (WSAGetLastError()==WSAEINTR)
580 return GWEN_ERROR_INTERRUPTED;
581 else
582 return WSAGetLastError();
583 }
584 *bsize=i;
585 return 0;
586 }
587
588
589
GWEN_Socket_SetBlocking(GWEN_SOCKET * sp,int b)590 int GWEN_Socket_SetBlocking(GWEN_SOCKET *sp,
591 int b)
592 {
593 unsigned long fl;
594
595 assert(sp);
596 fl=!b;
597 if (ioctlsocket(sp->socket, FIONBIO, &fl)) {
598 DBG_INFO(GWEN_LOGDOMAIN, "Error %d (%s)",
599 WSAGetLastError(),
600 GWEN_ErrorString_Windows(WSAGetLastError()));
601 return WSAGetLastError();
602 }
603 return 0;
604 }
605
606
607
GWEN_Socket_SetBroadcast(GWEN_SOCKET * sp,int fl)608 int GWEN_Socket_SetBroadcast(GWEN_SOCKET *sp,
609 int fl)
610 {
611
612 assert(sp);
613 if (sp->type==GWEN_SocketTypeUnix)
614 return 0;
615 if (setsockopt(sp->socket,
616 SOL_SOCKET,
617 SO_BROADCAST,
618 (const char *)&fl,
619 sizeof(fl)))
620 return WSAGetLastError();
621 return 0;
622 }
623
624
625
GWEN_Socket_SetReuseAddress(GWEN_SOCKET * sp,int fl)626 int GWEN_Socket_SetReuseAddress(GWEN_SOCKET *sp, int fl)
627 {
628 assert(sp);
629
630 /*if (sp->type==SocketTypeUnix)
631 return 0;*/
632
633 if (setsockopt(sp->socket,
634 SOL_SOCKET,
635 SO_REUSEADDR,
636 (const char *)&fl,
637 sizeof(fl)))
638 return WSAGetLastError();
639 return 0;
640 }
641
642
643
GWEN_Socket_GetSocketError(GWEN_SOCKET * sp)644 int GWEN_Socket_GetSocketError(GWEN_SOCKET *sp)
645 {
646 int rv;
647 int rvs;
648
649 assert(sp);
650 rvs=sizeof(rv);
651 if (-1==getsockopt(sp->socket, SOL_SOCKET, SO_ERROR, (char *)&rv, &rvs)) {
652 DBG_INFO(GWEN_LOGDOMAIN, "getsockopt(): %s", GWEN_ErrorString_Windows(WSAGetLastError()));
653 return GWEN_ERROR_IO;
654 }
655
656 if (rv) {
657 switch (rv) {
658 case WSAEWOULDBLOCK:
659 return GWEN_ERROR_TIMEOUT;
660 case WSAEINTR:
661 return GWEN_ERROR_INTERRUPTED;
662 default:
663 DBG_INFO(GWEN_LOGDOMAIN, "getsockopt(): %d (%s)", rv, GWEN_ErrorString_Windows(rv));
664 return GWEN_ERROR_IO;
665 }
666 }
667
668 return 0;
669 }
670
671
672
GWEN_Socket_WaitForRead(GWEN_SOCKET * sp,int timeout)673 int GWEN_Socket_WaitForRead(GWEN_SOCKET *sp, int timeout)
674 {
675 int err;
676 GWEN_SOCKETSET *set;
677
678 set=GWEN_SocketSet_new();
679
680 err=GWEN_SocketSet_AddSocket(set, sp);
681 if (err) {
682 GWEN_SocketSet_free(set);
683 return err;
684 }
685 err=GWEN_Socket_Select(set, 0, 0, timeout);
686 GWEN_SocketSet_free(set);
687
688 return err;
689 }
690
691
692
GWEN_Socket_WaitForWrite(GWEN_SOCKET * sp,int timeout)693 int GWEN_Socket_WaitForWrite(GWEN_SOCKET *sp, int timeout)
694 {
695 int err;
696 GWEN_SOCKETSET *set;
697
698 set=GWEN_SocketSet_new();
699 err=GWEN_SocketSet_AddSocket(set, sp);
700 if (err) {
701 GWEN_SocketSet_free(set);
702 return err;
703 }
704 err=GWEN_Socket_Select(0, set, 0, timeout);
705 GWEN_SocketSet_free(set);
706
707 return err;
708 }
709
710
711
GWEN_Socket_GetSocketType(GWEN_SOCKET * sp)712 GWEN_SOCKETTYPE GWEN_Socket_GetSocketType(GWEN_SOCKET *sp)
713 {
714 assert(sp);
715 return sp->type;
716 }
717
718
719
GWEN_Socket_GetSocketInt(const GWEN_SOCKET * sp)720 int GWEN_Socket_GetSocketInt(const GWEN_SOCKET *sp)
721 {
722 assert(sp);
723 return sp->socket;
724 }
725
726
727
GWEN_Socket_ErrorString(int c)728 const char *GWEN_Socket_ErrorString(int c)
729 {
730 const char *s;
731
732 switch (c) {
733 case 0:
734 s="Success";
735 break;
736 case GWEN_SOCKET_ERROR_BAD_SOCKETTYPE:
737 s="Bad socket type";
738 break;
739 case GWEN_SOCKET_ERROR_NOT_OPEN:
740 s="Socket not open";
741 break;
742 case GWEN_SOCKET_ERROR_TIMEOUT:
743 s="Socket timeout";
744 break;
745 case GWEN_SOCKET_ERROR_IN_PROGRESS:
746 s="Operation in progress";
747 break;
748 case GWEN_SOCKET_ERROR_INTERRUPTED:
749 s="Operation interrupted by system signal.";
750 break;
751 case GWEN_SOCKET_ERROR_ABORTED:
752 s="Operation aborted by user.";
753 break;
754 case GWEN_SOCKET_ERROR_BROKEN_PIPE:
755 s="Broken connection.";
756 break;
757 default:
758 if (c>0)
759 s=GWEN_ErrorString_Windows(c);
760 else
761 s=(const char *)0;
762 } /* switch */
763 return s;
764 }
765
766
767
768