bbb8666e039f119ed7c37ef87f9349b0a539ce5d
[tpg/acess2.git] / Usermode / Applications / ifconfig_src / main.c
1 /*
2  * Acess2 IFCONFIG command
3  */
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <string.h>
8 #include <acess/sys.h>
9 #include <net.h>
10
11 // === CONSTANTS ===
12 #define FILENAME_MAX    255
13 #define IPSTACK_ROOT    "/Devices/ip"
14 #define DEFAULT_METRIC  30
15
16 // TODO: Move this to a header
17 #define ntohs(v)        (((v&0xFF)<<8)|((v>>8)&0xFF))
18
19 // === PROTOTYPES ===
20 void    PrintUsage(const char *ProgName);
21 void    DumpInterfaces(void);
22 void    DumpRoutes(void);
23 void    DumpInterface(const char *Name);
24 void    DumpRoute(const char *Name);
25  int    AddInterface(const char *Device);
26 void    AddRoute(const char *Interface, int AddressType, void *Dest, int MaskBits, int Metric, void *NextHop);
27  int    DoAutoConfig(const char *Device);
28  int    SetAddress(int IFNum, const char *Address);
29  int    ParseIPAddress(const char *Address, uint8_t *Dest, int *SubnetBits);
30
31 // === CODE ===
32 /**
33  * \brief Program entrypoint
34  */
35 int main(int argc, char *argv[])
36 {
37          int    ret;
38         
39         // No args, dump interfaces
40         if(argc == 1) {
41                 DumpInterfaces();
42                 return 0;
43         }
44         
45         // Routes
46         if( strcmp(argv[1], "route") == 0 )
47         {
48                 // Add new route
49                 if( argc > 2 && strcmp(argv[2], "add") == 0 )
50                 {
51                         uint8_t dest[16] = {0};
52                         uint8_t nextHop[16] = {0};
53                          int    addrType, subnetBits = -1;
54                          int    nextHopType, nextHopBits=-1;
55                         char    *ifaceName = NULL;
56                          int    metric = DEFAULT_METRIC;
57                         // Usage:
58                         // ifconfig route add <host>[/<prefix>] <interface> [<metric>]
59                         // ifconfig route add <host>[/<prefix>] <next hop> [<metric>]
60                         if( argc - 3  < 2 ) {
61                                 fprintf(stderr, "ERROR: '%s route add' takes at least two arguments, %i passed\n",
62                                         argv[0], argc-3);
63                                 PrintUsage(argv[0]);
64                                 return -1;
65                         }
66                         
67                         if( argc - 3 > 3 ) {
68                                 fprintf(stderr, "ERROR: '%s route add' takes at most three arguments, %i passed\n",
69                                         argv[0], argc-3);
70                                 PrintUsage(argv[0]);
71                                 return -1;
72                         }
73                         
74                         // Destination IP
75                         addrType = ParseIPAddress(argv[3], dest, &subnetBits);
76                         if( subnetBits == -1 ) {
77                                 subnetBits = Net_GetAddressSize(addrType)*8;
78                         }
79                         // Interface Name / Next Hop
80                         if( (nextHopType = ParseIPAddress(argv[4], nextHop, &nextHopBits)) == 0 )
81                         {
82                                 // Interface name
83                                 ifaceName = argv[4];
84                         }
85                         else
86                         {
87                                 // Next Hop
88                                 // - Check if it's the same type as the network/destination
89                                 if( nextHopType != addrType ) {
90                                         fprintf(stderr, "ERROR: Address type mismatch\n");
91                                         return -1;
92                                 }
93                                 // - Make sure there's no mask
94                                 if( nextHopBits != -1 ) {
95                                         fprintf(stderr, "Error: Next hop cannot be masked\n");
96                                         return -1;
97                                 }
98                         }
99                         
100                         // Metric
101                         if( argc - 3 >= 3 )
102                         {
103                                 metric = atoi(argv[5]);
104                                 if( metric == 0 && argv[5][0] != '0' ) {
105                                         fprintf(stderr, "ERROR: Metric should be a number\n");
106                                         return -1;
107                                 }
108                         }
109                         
110                         // Make the route!
111                         AddRoute(ifaceName, addrType, dest, subnetBits, metric, nextHop);
112                         
113                         return 0;
114                 }
115                 // Delete a route
116                 else if( argc > 2 && strcmp(argv[2], "del") == 0 )
117                 {
118                         // Usage:
119                         // ifconfig route del <routenum>
120                         // ifconfig route del <host>[/<prefix>]
121                 }
122                 else
123                 {
124                         // List routes
125                         DumpRoutes();
126                 }
127                 return 0;
128         }
129         // Add a new interface
130         else if( strcmp(argv[1], "add") == 0 )
131         {
132                 if( argc < 4 ) {
133                         fprintf(stderr, "ERROR: '%s add' requires two arguments, %i passed\n", argv[0], argc-2);
134                         PrintUsage(argv[0]);
135                         return -1;
136                 }
137                 ret = AddInterface( argv[2] );
138                 if(ret < 0)     return ret;
139                 ret = SetAddress( ret, argv[3] );
140                 return ret;
141         }
142         // Delete an interface
143         else if( strcmp(argv[1], "del") == 0 )
144         {
145                 if( argc < 3 ) {
146                         fprintf(stderr, "ERROR: '%s del' requires an argument\n", argv[0]);
147                         PrintUsage(argv[0]);
148                         return -1;
149                 }
150                 // TODO:
151         }
152         // Autoconfigure an interface
153         // NOTE: Debugging hack (see the function for more details)
154         else if( strcmp(argv[1], "autoconf") == 0 )
155         {
156                 DoAutoConfig(argv[2]);
157                 return 0;
158         }
159         else if( strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 )
160         {
161                 PrintUsage(argv[0]);
162                 return 0;
163         }
164         
165         // Dump a named interface
166         DumpInterface(argv[1]);
167         
168         return 0;
169 }
170
171 /**
172  * \brief Print usage instructions
173  */
174 void PrintUsage(const char *ProgName)
175 {
176         fprintf(stderr, "Usage:\n");
177         fprintf(stderr, "    %s add <device> <ip>/<prefix>\n", ProgName);
178         fprintf(stderr, "        Add a new interface listening on <device> with the specified\n");
179         fprintf(stderr, "        address.\n");
180         fprintf(stderr, "    %s del <interface>\n", ProgName);
181         fprintf(stderr, "        Delete an interface\n");
182         fprintf(stderr, "    %s [<interface>]\n", ProgName);
183         fprintf(stderr, "        Print the current interfaces (or only <interface> if passed)\n");
184         fprintf(stderr, "\n");
185         fprintf(stderr, "    %s route\n", ProgName);
186         fprintf(stderr, "        Print the routing tables\n");
187         fprintf(stderr, "    %s route add <host>[/<prefix>] [<nexthop> OR <iface>] [<metric>]\n", ProgName);
188         fprintf(stderr, "        Add a new route\n");
189         fprintf(stderr, "    %s route del <host>[/<prefix>]\n", ProgName);
190         fprintf(stderr, "    %s route del <routenum>\n", ProgName);
191         fprintf(stderr, "        Add a new route\n");
192         fprintf(stderr, "\n");
193         fprintf(stderr, "A note on Acess's IP Stack:\n");
194         fprintf(stderr, "    Each interface corresponds to only one IP address (either IPv4\n");
195         fprintf(stderr, "    or IPv6). A network device can have multiple interfaces bound\n");
196         fprintf(stderr, "    to it, allowing multiple addresses for one network connection\n");
197         fprintf(stderr, "\n");
198 }
199
200 /**
201  * \brief Dump all interfaces
202  */
203 void DumpInterfaces(void)
204 {
205          int    dp;
206         char    filename[FILENAME_MAX+1];
207         
208         dp = open(IPSTACK_ROOT, OPENFLAG_READ);
209         
210         while( readdir(dp, filename) )
211         {
212                 if(filename[0] == '.')  continue;
213                 DumpInterface(filename);
214         }
215         
216         close(dp);
217 }
218
219 /**
220  * \brief Dump all interfaces
221  */
222 void DumpRoutes(void)
223 {
224          int    dp;
225         char    filename[FILENAME_MAX+1];
226         
227         dp = open(IPSTACK_ROOT"/routes", OPENFLAG_READ);
228         
229         printf("Type\tNetwork \tGateway \tMetric\tIFace\n");
230         
231         while( readdir(dp, filename) )
232         {
233                 if(filename[0] == '.')  continue;
234                 DumpRoute(filename);
235         }
236         
237         close(dp);
238 }
239
240 /**
241  * \brief Dump an interface
242  */
243 void DumpInterface(const char *Name)
244 {
245          int    fd;
246          int    type;
247         char    path[sizeof(IPSTACK_ROOT)+1+FILENAME_MAX+1] = IPSTACK_ROOT"/";
248         
249         strcat(path, Name);
250         
251         fd = open(path, OPENFLAG_READ);
252         if(fd == -1) {
253                 fprintf(stderr, "Bad interface name '%s' (%s does not exist)\t", Name, path);
254                 return ;
255         }
256         
257         type = ioctl(fd, 4, NULL);
258         
259         // Ignore -1 values
260         if( type == -1 ) {
261                 return ;
262         }
263         
264         printf("%s:\t", Name);
265         {
266                  int    call_num = ioctl(fd, 3, "get_device");
267                  int    len = ioctl(fd, call_num, NULL);
268                 char    *buf = malloc(len+1);
269                 ioctl(fd, call_num, buf);
270                 printf("'%s'\n", buf);
271                 free(buf);
272         }
273         printf("\t");
274         // Get the address type
275         switch(type)
276         {
277         case 0: // Disabled/Unset
278                 printf("DISABLED\n");
279                 break;
280         case 4: // IPv4
281                 {
282                 uint8_t ip[4];
283                  int    subnet;
284                 printf("IPv4\t");
285                 ioctl(fd, 5, ip);       // Get IP Address
286                 subnet = ioctl(fd, 7, NULL);    // Get Subnet Bits
287                 printf("%i.%i.%i.%i/%i\n", ip[0], ip[1], ip[2], ip[3], subnet);
288                 }
289                 break;
290         case 6: // IPv6
291                 {
292                 uint16_t        ip[8];
293                  int    subnet;
294                 printf("IPv6\t");
295                 ioctl(fd, 5, ip);       // Get IP Address
296                 subnet = ioctl(fd, 7, NULL);    // Get Subnet Bits
297                 printf("%x:%x:%x:%x:%x:%x:%x:%x/%i\n",
298                         ntohs(ip[0]), ntohs(ip[1]), ntohs(ip[2]), ntohs(ip[3]),
299                         ntohs(ip[4]), ntohs(ip[5]), ntohs(ip[6]), ntohs(ip[7]),
300                         subnet);
301                 }
302                 break;
303         default:        // Unknow
304                 printf("UNKNOWN (%i)\n", type);
305                 break;
306         }
307                         
308         close(fd);
309 }
310
311
312 /**
313  * \brief Dump a route
314  */
315 void DumpRoute(const char *Name)
316 {
317          int    fd;
318          int    type;
319         char    path[sizeof(IPSTACK_ROOT)+8+FILENAME_MAX+1] = IPSTACK_ROOT"/routes/";
320         
321         strcat(path, Name);
322         
323         fd = open(path, OPENFLAG_READ);
324         if(fd == -1) {
325                 printf("%s:\tUnable to open ('%s')\n", Name, path);
326                 return ;
327         }
328         
329          int    ofs = 2;
330         type = atoi(Name);
331         
332          int    i;
333          int    len = Net_GetAddressSize(type);
334         uint8_t net[len], gw[len];
335          int    subnet, metric;
336         for( i = 0; i < len; i ++ ) {
337                 char tmp[5] = "0x00";
338                 tmp[2] = Name[ofs++];
339                 tmp[3] = Name[ofs++];
340                 net[i] = atoi(tmp);
341         }
342         ofs ++;
343         subnet = atoi(Name+ofs);
344         ofs ++;
345         metric = atoi(Name+ofs);
346         ioctl(fd, ioctl(fd, 3, "get_nexthop"), gw);     // Get Gateway/NextHop
347         
348         // Get the address type
349         switch(type)
350         {
351         case 0: // Disabled/Unset
352                 printf("DISABLED\n");
353                 break;
354         case 4: // IPv4
355                 printf("IPv4\t");
356                 break;
357         case 6: // IPv6
358                 printf("IPv6\t");
359                 break;
360         default:        // Unknow
361                 printf("UNKNOWN (%i)\n", type);
362                 break;
363         }
364         printf("%s/%i\t", Net_PrintAddress(type, net), subnet);
365         printf("%s \t", Net_PrintAddress(type, gw));
366         printf("%i\t", metric);
367         
368         // Interface
369         {
370                  int    call_num = ioctl(fd, 3, "get_interface");
371                  int    len = ioctl(fd, call_num, NULL);
372                 char    *buf = malloc(len+1);
373                 ioctl(fd, call_num, buf);
374                 printf("'%s'\t", buf);
375                 free(buf);
376         }
377         
378         printf("\n");
379                         
380         close(fd);
381 }
382
383 /**
384  * \brief Create a new interface using the passed device
385  * \param Device        Network device to bind to
386  */
387 int AddInterface(const char *Device)
388 {
389          int    dp, ret;
390         
391         dp = open(IPSTACK_ROOT, OPENFLAG_READ);
392         ret = ioctl(dp, 4, (void*)Device);
393         close(dp);
394         
395         if( ret < 0 ) {
396                 fprintf(stderr, "Unable to add '%s' as a network interface\n", Device);
397                 return -1;
398         }
399         
400         printf("-- Added '"IPSTACK_ROOT"/%i' using device %s\n", ret, Device);
401         
402         return ret;
403 }
404
405 void AddRoute(const char *Interface, int AddressType, void *Dest, int MaskBits, int Metric, void *NextHop)
406 {
407          int    fd;
408          int    num;
409         char    *ifaceToFree = NULL;
410         
411         // Get interface name
412         if( !Interface )
413         {
414                 if( !NextHop ) {
415                         fprintf(stderr,
416                                 "BUG: AddRoute(Interface=NULL,...,NextHop=NULL)\n"
417                                 "Only one should be NULL\n"
418                                 );
419                         return ;
420                 }
421                 
422                 // Query for the interface name
423                 Interface = ifaceToFree = Net_GetInterface(AddressType, NextHop);
424         }
425         // Check address type (if the interface was passed)
426         // - If we got the interface name, then it should be correct
427         else
428         {
429                 char    ifacePath[sizeof(IPSTACK_ROOT"/")+strlen(Interface)+1];
430                 
431                 // Open interface
432                 strcpy(ifacePath, IPSTACK_ROOT"/");
433                 strcat(ifacePath, Interface);
434                 fd = open(ifacePath, 0);
435                 if( fd == -1 ) {
436                         fprintf(stderr, "Error: Interface '%s' does not exist\n", Interface);
437                         return ;
438                 }
439                 // Get and check type
440                 num = ioctl(fd, ioctl(fd, 3, "getset_type"), NULL);
441                 if( num != AddressType ) {
442                         fprintf(stderr, "Error: Passed type does not match interface type (%i != %i)\n",
443                                 AddressType, num);
444                         return ;
445                 }
446                 
447                 close(fd);
448         }
449         
450         // Create route
451          int    addrsize = Net_GetAddressSize(AddressType);
452          int    len = snprintf(NULL, 0, "/Devices/ip/routes/%i::%i:%i", AddressType, MaskBits, Metric) + addrsize*2;
453         char    path[len+1];
454         {
455                  int    i, ofs;
456                 ofs = sprintf(path, "/Devices/ip/routes/%i:", AddressType);
457                 for( i = 0; i < addrsize; i ++ )
458                         sprintf(path+ofs+i*2, "%02x", ((uint8_t*)Dest)[i]);
459                 ofs += addrsize*2;
460                 sprintf(path+ofs, ":%i:%i", MaskBits, Metric);
461         }
462
463         fd = open(path, 0);
464         if( fd != -1 ) {
465                 close(fd);
466                 fprintf(stderr, "Unable to create route '%s', already exists\n", path);
467                 return ;
468         }
469         fd = open(path, OPENFLAG_CREATE, 0);
470         if( fd == -1 ) {
471                 fprintf(stderr, "Unable to create '%s'\n", path);
472                 return ;
473         }
474         
475         if( NextHop )
476                 ioctl(fd, ioctl(fd, 3, "set_nexthop"), NextHop);
477         ioctl(fd, ioctl(fd, 3, "set_interface"), (void*)Interface);
478         
479         close(fd);
480         
481         // Check if the interface name was allocated by us
482         if( ifaceToFree )
483                 free(ifaceToFree);
484 }
485
486 /**
487  * \note Debugging HACK!
488  * \brief Autoconfigure the specified device to 10.0.2.55/24 using
489  *        10.0.2.2 as the gateway.
490  */
491 int DoAutoConfig(const char *Device)
492 {
493          int    tmp, fd;
494         char    path[sizeof(IPSTACK_ROOT)+1+4+1];       // /0000
495         uint8_t addr[4] = {10,0,2,55};
496         uint8_t gw[4] = {10,0,2,2};
497          int    subnet = 24;
498         
499         tmp = AddInterface(Device);
500         if( tmp < 0 )   return tmp;
501         
502         sprintf(path, IPSTACK_ROOT"/%i", tmp);
503         
504         fd = open(path, OPENFLAG_READ);
505         if( fd == -1 ) {
506                 fprintf(stderr, "Unable to open '%s'\n", path);
507                 return -1;
508         }
509         
510         tmp = 4;        // IPv4
511         tmp = ioctl(fd, ioctl(fd, 3, "getset_type"), &tmp);
512         if( tmp != 4 ) {
513                 fprintf(stderr, "Error in setting address type (got %i, expected 4)\n", tmp);
514                 return -1;
515         }
516         // Set Address
517         ioctl(fd, ioctl(fd, 3, "set_address"), addr);
518         // Set Subnet
519         ioctl(fd, ioctl(fd, 3, "getset_subnet"), &subnet);
520         
521         // Set routes
522         {
523                 uint8_t net[4] = {0,0,0,0};
524                 AddRoute(path + sizeof(IPSTACK_ROOT), 4, addr, subnet, DEFAULT_METRIC, net);    // This interface
525                 AddRoute(path + sizeof(IPSTACK_ROOT), 4, net, 0, DEFAULT_METRIC, gw);   // Gateway
526         }
527         
528         close(fd);
529         
530         printf("Set address to %i.%i.%i.%i/%i (GW: %i.%i.%i.%i)\n",
531                 addr[0], addr[1], addr[2], addr[3],
532                 subnet,
533                 gw[0], gw[1], gw[2], gw[3]);
534         
535         return 0;
536 }
537
538 /**
539  * \brief Set the address on an interface from a textual IP address
540  */
541 int     SetAddress(int IFNum, const char *Address)
542 {
543         uint8_t addr[16];
544          int    type;
545         char    path[sizeof(IPSTACK_ROOT)+1+5+1];       // ip000
546          int    tmp, fd, subnet;
547         
548         // Parse IP Address
549         type = ParseIPAddress(Address, addr, &subnet);
550         if(type == 0) {
551                 fprintf(stderr, "'%s' cannot be parsed as an IP address\n", Address);
552                 return -1;
553         }
554         
555         // Open file
556         sprintf(path, IPSTACK_ROOT"/%i", IFNum);
557         fd = open(path, OPENFLAG_READ);
558         if( fd == -1 ) {
559                 fprintf(stderr, "Unable to open '%s'\n", path);
560                 return -1;
561         }
562         
563         tmp = type;
564         tmp = ioctl(fd, ioctl(fd, 3, "getset_type"), &tmp);
565         if( tmp != type ) {
566                 fprintf(stderr, "Error in setting address type (got %i, expected %i)\n", tmp, type);
567                 close(fd);
568                 return -1;
569         }
570         // Set Address
571         ioctl(fd, ioctl(fd, 3, "set_address"), addr);
572         
573         // Set Subnet
574         ioctl(fd, ioctl(fd, 3, "getset_subnet"), &subnet);
575         
576         close(fd);
577         
578         // Dump!
579         //DumpInterface( path+sizeof(IPSTACK_ROOT)+1 );
580         
581         return 0;
582 }
583
584 /**
585  * \brief Parse an IP Address
586  * \return 0 for unknown, 4 for IPv4 and 6 for IPv6
587  */
588 int ParseIPAddress(const char *Address, uint8_t *Dest, int *SubnetBits)
589 {
590         const char      *p = Address;
591         
592         // Check first block
593         while(*p && *p >= '0' && *p <= '9')     p ++;
594         
595         // IPv4?
596         if(*p == '.')
597         {
598                  int    i = 0, j;
599                  int    val;
600                 
601                 for( j = 0; Address[i] && j < 4; j ++ )
602                 {
603                         val = 0;
604                         for( ; '0' <= Address[i] && Address[i] <= '9'; i++ )
605                         {
606                                 val = val*10 + Address[i] - '0';
607                         }
608                         if(val > 255) {
609                                 //printf("val > 255 (%i)\n", val);
610                                 return 0;
611                         }
612                         Dest[j] = val;
613                         
614                         if(Address[i] == '.')
615                                 i ++;
616                 }
617                 if( j != 4 ) {
618                         //printf("4 parts expected, %i found\n", j);
619                         return 0;
620                 }
621                 // Parse subnet size
622                 if(Address[i] == '/') {
623                         val = 0;
624                         i ++;
625                         while('0' <= Address[i] && Address[i] <= '9') {
626                                 val *= 10;
627                                 val += Address[i] - '0';
628                                 i ++;
629                         }
630                         if(val > 32) {
631                                 printf("Notice: Subnet size >32 (%i)\n", val);
632                         }
633                         if(SubnetBits)  *SubnetBits = val;
634                 }
635                 if(Address[i] != '\0') {
636                         //printf("EOS != '\\0', '%c'\n", Address[i]);
637                         return 0;
638                 }
639                 return 4;
640         }
641         
642         // IPv6
643         if(*p == ':' || ('a' <= *p && *p <= 'f') || ('A' <= *p && *p <= 'F'))
644         {
645                  int    i = 0;
646                  int    j, k;
647                  int    val, split = -1, end;
648                 uint16_t        hi[8], low[8];
649                 
650                 for( j = 0; Address[i] && j < 8; j ++ )
651                 {
652                         if(Address[i] == '/')
653                                 break;
654                         
655                         if(Address[i] == ':') {
656                                 if(split != -1) {
657                                         printf("Two '::'s\n");
658                                         return 0;
659                                 }
660                                 split = j;
661                                 i ++;
662                                 continue;
663                         }
664                         
665                         val = 0;
666                         for( k = 0; Address[i] && Address[i] != ':' && Address[i] != '/'; i++, k++ )
667                         {
668                                 val *= 16;
669                                 if('0' <= Address[i] && Address[i] <= '9')
670                                         val += Address[i] - '0';
671                                 else if('A' <= Address[i] && Address[i] <= 'F')
672                                         val += Address[i] - 'A' + 10;
673                                 else if('a' <= Address[i] && Address[i] <= 'f')
674                                         val += Address[i] - 'a' + 10;
675                                 else {
676                                         printf("%c unexpected\n", Address[i]);
677                                         return 0;
678                                 }
679                         }
680                         
681                         if(val > 0xFFFF) {
682                                 printf("val (0x%x) > 0xFFFF\n", val);
683                                 return 0;
684                         }
685                         
686                         if(split == -1)
687                                 hi[j] = val;
688                         else
689                                 low[j-split] = val;
690                         
691                         if( Address[i] == ':' ) {
692                                 i ++;
693                         }
694                 }
695                 end = j;
696                 
697                 // Parse subnet size
698                 if(Address[i] == '/') {
699                         val = 0;
700                         while('0' <= Address[i] && Address[i] <= '9') {
701                                 val *= 10;
702                                 val += Address[i] - '0';
703                                 i ++;
704                         }
705                         if(val > 128) {
706                                 printf("Notice: Subnet size >128 (%i)\n", val);
707                         }
708                         if(SubnetBits)  *SubnetBits = val;
709                 }
710                 
711                 for( j = 0; j < split; j ++ )
712                 {
713                         //printf("%04x:", hi[j]);
714                         Dest[j*2] = hi[j]>>8;
715                         Dest[j*2+1] = hi[j]&0xFF;
716                 }
717                 for( ; j < 8 - (end - split); j++ )
718                 {
719                         //printf("0000:", hi[j]);
720                         Dest[j*2] = 0;
721                         Dest[j*2+1] = 0;
722                 }
723                 for( k = 0; j < 8; j ++, k++)
724                 {
725                         //printf("%04x:", low[k]);
726                         Dest[j*2] = low[k]>>8;
727                         Dest[j*2+1] = low[k]&0xFF;
728                 }
729                 return 6;
730         }
731         // Unknown type
732         return 0;
733 }

UCC git Repository :: git.ucc.asn.au