The Styleguide section has been moved to the Wireshark Developer's Guide.
[obnox/wireshark/wip.git] / mkcap.c
1 /* mkcap.c
2  * A small program to generate the ASCII form of a capture with TCP
3  * segments of a reasonable nature. The payload is all zeros.
4  *
5  * $Id$
6  *
7  * By Ronnie Sahlberg and Richard Sharpe. From a program initially 
8  * written by Ronnie.
9  * Copyright 2003 Ronnie Sahlberg and Richard Sharpe
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  *
25  * Using it to generate a capture file:
26  * ./mkcap [some-flags] > some-file
27  * text2pcap [some-other-flags] some-file some-file.cap
28  * For example:
29
30 ./mkcap -a 2500 -s 15 -I "02 03 04 05" -i "45 45 45 45" -P "00 14"  > ftp.cap.asci
31 text2pcap -t "%Y/%m/%d%t%H:%M:%S." ftp.cap.asci ftp.cap
32
33  */
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38
39 #define ETH_1 "00 00 9c fa 1d 74"
40 #define ETH_2 "00 1a b8 93 f6 71"
41 #define IP_1  "0a 01 01 17"
42 #define IP_2  "0a 01 01 ea"
43 #define PORT_1 "01 00"
44 #define PORT_2 "10 00"
45
46 char *eth_1 = ETH_1;
47 char *eth_2 = ETH_2;
48 char *ip_1 = IP_1;
49 char *ip_2 = IP_2;
50 char *port_1 = PORT_1;
51 char *port_2 = PORT_2;
52
53 int verbose = 0;
54
55 typedef enum {
56   normal = 0,
57   random_ack_drop = 1,
58   random_data_drop = 2,
59 } run_type_t;
60
61 typedef struct {
62   int drop_seg_start;
63   int drop_seg_count;
64 } seg_drop_t;
65
66 /*
67  * The array of which segments should be dropped ...
68  */
69 seg_drop_t *drops = NULL;
70 int seg_drop_count = 0;
71 /* The array of which ACKs should be dropped. This is complicated because
72  * An ack might not be generated for a segment because of delayed ACKs.
73  */
74 seg_drop_t *ack_drops = NULL;
75 int ack_drop_count = 0;
76
77 int total_bytes = 32768;
78 int run_type = 0;
79
80 int seq_2=0;
81 int seq_1=0;
82 int ts=0;
83 int jitter = 0;
84 int send_spacing = 10;
85 int ack_delay = 5000;
86 int tcp_nodelay = 0;
87 int tcp_delay_time = 1000; /* What is the real time here? */
88 /*
89  * If tcp_nodelay is set, then this is the amount of data left ... 
90  */
91 int remaining_data = 0;
92 int snap_len = 1500;
93 int window = 32768;
94 int ssthresh = 16384;
95 int cwnd = 1460;
96 int used_win = 0;
97 int segment = 0;
98
99 #define SEG_ACK_LOST 1
100 #define SEG_SEG_LOST 2
101
102 struct seg_hist_s {
103   int seq_num;           /* First sequence number in segment     */
104   int len;               /* Number of bytes in segment           */
105   int ts;                /* Timestamp when sent                  */
106   int seg_num;           /* Segment number sent. This can change */
107                          /* but a retransmit will have a new seg */
108   int flags;             /* Flags as above for ack and seg loss  */
109   int acks_first_seq;    /* How many times we have seen an ack 
110                             for the first seq number in this seg */
111 };
112
113 #define SEG_HIST_SIZE 128
114 struct seg_hist_s seg_hist[128];    /* This should be dynamic */
115 int next_slot = 0;
116 int first_slot = 0;
117
118 int delayed_ack = 1;          /* Default is delayed ACKs in use ...  */
119 int delayed_ack_wait = 30000; /* 30 mS before an ACK is generated if */
120                               /* no other traffic                    */
121
122 void
123 makeseg(char *eth1, char *eth2, char *ip1, char *ip2, char *p1, char *p2, int *s1, int *s2, char *flags, int len)
124 {
125         int i;
126
127         printf("2002/01/07 00:00:%02d.%06d\n", ts/1000000, ts%1000000);
128         printf("0000 %s %s 08 00\n", eth1, eth2);
129         printf("000e 45 00 %02x %02x 00 00 00 00 40 06 00 00 %s %s\n", (len+40)>>8, (len+40)&0xff, ip1, ip2);
130         printf("0022 %s %s %02x %02x %02x %02x %02x %02x %02x %02x 50 %s 80 00 00 00 00 00", p1, p2, 
131                 ((*s1)>>24)&0xff,
132                 ((*s1)>>16)&0xff,
133                 ((*s1)>>8)&0xff,
134                 ((*s1))&0xff,
135                 ((*s2)>>24)&0xff,
136                 ((*s2)>>16)&0xff,
137                 ((*s2)>>8)&0xff,
138                 ((*s2))&0xff,
139                 flags );
140         for(i=0;i<(len<(snap_len-40)?len:snap_len-40);i++)printf(" 00");
141         printf("\n");
142         printf("\n");
143         (*s1)+=len;
144 }
145
146 /*
147  * Figure out when the next ack is due ... here we must skip the acks for
148  * frames that are marked as ACKs dropped as well as the frames marked as
149  * frames dropped. These will be marked by the routine that generates ACKs.
150  * Returns a timestamp value. Returns 2^^31-1 if none are due at all
151  */
152 int next_ack_due()
153 {
154   int slot = next_slot;
155   int ack_lost = 0, seg_lost = 0;
156
157   if (next_slot == first_slot) 
158     return (((unsigned int)(1<<31)) - 1);
159
160   /*
161    * Figure out if we need to issue an ACK. We skip all outstanding packets
162    * that are marked as ack lost or packet lost.
163    * 
164    * We would not usually come in here with a frame marked as lost or ack lost
165    * rather, we will come in here and specify that the ack was due at a
166    * certain time, and gen_next_ack would then determine that the ack
167    * should be lost or the packet lost.
168    */
169
170   /*
171    * Look for a seg slot that is not lost or dropped
172    */
173
174   while (seg_hist[slot].flags & (SEG_ACK_LOST || SEG_SEG_LOST)) {
175     if (seg_hist[slot].flags & SEG_ACK_LOST)
176       ack_lost++;
177     if (seg_hist[slot].flags & SEG_SEG_LOST)
178       seg_lost++;
179     slot = (slot + 1) % SEG_HIST_SIZE;
180   }
181
182   if (slot == next_slot) 
183     return (((unsigned int)(1<<31)) - 1);
184
185   /* 
186    * If there is only one slot occupied, or a segment was lost then
187    * an ACK is due after the last [good] segment left plus ack_delay
188    */
189
190   if (slot == first_slot && next_slot == ((first_slot + 1) % SEG_HIST_SIZE))
191     return (seg_hist[first_slot].ts + ack_delay + jitter);
192
193   if (seg_lost) 
194     return (seg_hist[slot].ts + ack_delay + jitter);
195
196   /*
197    * OK, now, either we have only seen lost acks, or there are more than
198    * one outstanding segments, so figure out when the ACK is due.
199    *
200    * If delayed ACK is in force, ACK is due after every second seg, but
201    * if we had a lost ack, then we must ignore 2*lost_ack segments. So,
202    * if there has not been that many segments sent, we return infinity
203    * as the next ACK time
204    */
205
206   if (ack_lost) {
207     if (delayed_ack) {
208       if (((first_slot + 1 + 2 * ack_lost) % SEG_HIST_SIZE) >= next_slot)
209         /* XXX: FIXME, what about when the window is closed */
210         /* XXX: FIXME, use the correct value for this       */
211         return (((unsigned int)(1<<31)) - 1);
212       else 
213         return seg_hist[(first_slot + 1 + 2 * ack_lost) % SEG_HIST_SIZE].ts +
214           ack_delay + jitter;
215     }
216     else
217       return seg_hist[slot].ts + ack_delay + jitter;
218   }
219   else {
220     if (delayed_ack)
221       return (seg_hist[(first_slot + 1)%SEG_HIST_SIZE].ts+ack_delay+jitter);
222     else
223       return (seg_hist[first_slot].ts+ack_delay+jitter);
224   }
225 }
226
227 /*
228  * Update the relevant info of the sent seg
229  */
230 add_seg_sent(int seq, int len)
231 {
232
233   /* 
234    * Should check we have not wrapped around and run into the unacked
235    * stuff ...
236    */
237   /*if (next_slot == first_slot) ;*/
238
239   segment++;
240   seg_hist[next_slot].seq_num        = seq;
241   seg_hist[next_slot].len            = len;
242   seg_hist[next_slot].ts             = ts;
243   seg_hist[next_slot].seg_num        = segment;
244   seg_hist[next_slot].flags          = 0;
245   seg_hist[next_slot].acks_first_seq = 0;
246   used_win = used_win + len;          /* Update the window used */
247
248   /*
249    * Now, update next_slot ...
250    */
251
252   next_slot = (next_slot + 1) % SEG_HIST_SIZE;
253   
254 }
255
256 /*
257  * Generate the next ack based on the above reasoning ...
258  */
259
260 #define NO_FORCE_ACK 0
261 #define FORCE_ACK 1
262
263 /*
264  * Generate the next ACK. If we did not generate an ACK, return 0,
265  * else return 1.
266  */
267 int
268 gen_next_ack(int force, int spacing)
269 {
270   int seq_to_ack, new_ts, data_acked;
271
272   /* 
273    * We need to check if the segment that we are about to generate an
274    * ack for is a segment that should be dropped ... or an ack that should
275    * be dropped.
276    *
277    * Figure out what we are doing before freeing segments ...
278    */
279
280   seq_to_ack = seg_hist[first_slot].seq_num + seg_hist[first_slot].len;
281   used_win = used_win - seg_hist[first_slot].len;
282   data_acked = seg_hist[first_slot].len;
283   new_ts = seg_hist[first_slot].ts + ack_delay;
284   first_slot = (first_slot + 1) % SEG_HIST_SIZE;
285
286   /*
287    * If delayed ACK in force, then ACK the next segment if there is one
288    */
289   if (delayed_ack && (first_slot != next_slot)) {
290     seq_to_ack += seg_hist[first_slot].len;
291     used_win = used_win - seg_hist[first_slot].len;
292     data_acked += seg_hist[first_slot].len;
293     new_ts = seg_hist[first_slot].ts + ack_delay;
294     first_slot = (first_slot + 1) % SEG_HIST_SIZE;
295   }
296
297   /*
298    * We don't want time to go backward ...
299    */
300   if (new_ts + jitter <= ts)
301     ts++;
302   else 
303     ts = new_ts + jitter;
304
305   jitter = (rand() % 10 - 5);  /* Update jitter ... */
306
307   makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &seq_to_ack, "10", 0);
308   /*
309    * Do we want the exponential part or the linear part?
310    */
311   if (cwnd >= ssthresh)
312     cwnd += (1460*data_acked)/cwnd;      /* is this right? */
313   else
314     cwnd = cwnd + data_acked; 
315   if (verbose) fprintf(stderr, "Ack rcvd. ts: %d, data_acked: %d, cwnd: %d, window: %d\n", 
316           ts, data_acked, cwnd, window);
317   if (cwnd > window) cwnd = window;
318 }
319
320 void 
321 makeackedrun(int len, int spacing, int ackdelay)
322 {
323         int old_seq1, next_ack_ts=0;
324         if (verbose) fprintf(stderr, "makeackedrun: Len=%d, spacing=%d, ackdelay=%d\n",
325                 len, spacing, ackdelay);
326         old_seq1=seq_1;
327         while(len>0){
328
329           /*
330            * Each time we output a segment, we should check to see if an
331            * ack is due back before the next segment is due ...
332            */
333                 int seglen, saved_seq;
334                 seglen=(len>1460)?1460:len;
335                 /*
336                  * Only output what is left in the cwnd.
337                  * We assume there is space in the congestion window here
338                  */
339                 if (seglen > (cwnd - used_win)) seglen = cwnd - used_win;
340
341                 len-=seglen;
342                 saved_seq = seq_1;
343                 if (verbose) fprintf(stderr, "Sending segment. ts: %d, jitter: %d\n", ts, jitter);
344                 if(len){
345                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &seq_1, &seq_2, "10", seglen);
346                 } else {
347                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &seq_1, &seq_2, "18", seglen);
348                 }
349                 add_seg_sent(saved_seq, seglen);
350
351                 /*
352                  * Now, if the window is closed, then we have to eject an
353                  * ack, otherwise we can eject more data.
354                  * Also, the other end will tend to ack two segments at
355                  * a time ... and that ack might fall between two 
356                  * outgoing segments
357                  */
358                 jitter = (rand()%10) - 5; /* What if spacing too small */
359
360                 if (verbose) fprintf(stderr, "used win: %d, cwnd: %d\n", used_win, cwnd);
361
362                 if ((next_ack_ts = next_ack_due()) < ts + spacing + jitter) {
363                   int old_ts = ts;
364
365                   /*
366                    * Generate the ack and retire the segments
367                    * If delayed ACK in use, there should be two
368                    * or more outstanding segments ...
369                    */
370                   if (verbose) fprintf(stderr, "Non forced ACK ...ts + spacing + jitter:%d, jitter: %d\n", ts + spacing + jitter, jitter);
371                   gen_next_ack(NO_FORCE_ACK, spacing);
372                   /*
373                    * We don't want time to go backwards ... 
374                    */
375                   if (old_ts + spacing + jitter <= ts)
376                     ts++;
377                   else
378                     ts = old_ts + spacing + jitter;
379
380                 } else if (used_win == cwnd) {
381                   
382                   /*
383                    * We need an ACK, so generate it and retire the 
384                    * segments and advance the ts to the time of the ack
385                    */
386
387                   if (verbose) fprintf(stderr, "Forced ACK ... \n");
388                   gen_next_ack(FORCE_ACK, spacing);
389
390                   ts+=(spacing+jitter);   /* Should not use spacing here */
391
392                 }
393                 else {
394                   ts+=(spacing+jitter);
395                 }
396
397                 if (verbose) fprintf(stderr, "Next Ack Due: %d\n", next_ack_ts);
398         }
399
400 }
401
402
403 void 
404 makeackedrundroppedtail8kb(int len, int spacing, int ackdelay)
405 {
406         int old_seq1;
407         int dropped_tail;
408         int i;
409         int num_dupes;
410         if (verbose) fprintf(stderr, "makeackedrundroppedtail8kB: Len=%d, spacing=%d, ackdelay=%d\n",
411                 len, spacing, ackdelay);
412         old_seq1=seq_1;
413         while(len>0){
414                 int seglen;
415                 seglen=(len>1460)?1460:len;
416                 len-=seglen;
417                 if(seglen==1460){
418                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &seq_1, &seq_2, "10", seglen);
419                 } else {
420                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &seq_1, &seq_2, "18", seglen);
421                 }
422                 ts+=spacing;
423         }
424
425         ts+=ackdelay;
426
427         i=0;
428         num_dupes=-1;
429         dropped_tail=0;
430         while(old_seq1!=seq_1){
431                 int ack_len;
432
433                 ack_len=((seq_1-old_seq1)>2920)?2920:(seq_1-old_seq1);
434
435                 i++;
436                 if(i==6){
437                         dropped_tail=old_seq1;
438                 }
439                 old_seq1+=ack_len;
440                 if(i<6){
441                         makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &old_seq1, "10", 0);
442                 } else if (i==6) {
443                         makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &dropped_tail, "10", 0);
444                         num_dupes+=2;
445                 } else {
446                         makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &dropped_tail, "10", 0);
447                         makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &dropped_tail, "10", 0);
448                         num_dupes+=2;
449                 }
450                 ts+=spacing/2;
451         }
452
453         if(!dropped_tail){
454                 return;
455         }
456
457         if(num_dupes<3){
458                 int seglen;
459                 int new_seq;
460                 ts+=1000000;
461                 seglen=((seq_1-dropped_tail)>1460)?1460:(seq_1-dropped_tail);
462                 if(seglen==1460){
463                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &dropped_tail, &seq_2, "10", seglen);
464                 } else {
465                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &dropped_tail, &seq_2, "18", seglen);
466                 }
467                 ts+=ackdelay;
468
469                 new_seq=seglen+seq_1;
470                 makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &seq_1, "10", 0);
471                 ts+=spacing;
472                 return;
473         }
474
475         while(dropped_tail!=seq_1){
476                 int seglen;
477                 int ack;
478                 seglen=((seq_1-dropped_tail)>1460)?1460:(seq_1-dropped_tail);
479                 if(seglen==1460){
480                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &dropped_tail, &seq_2, "10", seglen);
481                 } else {
482                         makeseg(eth_1, eth_2, ip_1, ip_2, port_1, port_2, &dropped_tail, &seq_2, "18", seglen);
483                 }
484                 ts+=ackdelay;
485
486                 ack=dropped_tail;
487                 makeseg(eth_2, eth_1, ip_2, ip_1, port_2, port_1, &seq_2, &ack, "10", 0);
488                 ts+=spacing;
489         }
490 }
491
492 void usage()
493 {
494   fprintf(stderr, "Usage: mkcap [OPTIONS], where\n");
495   fprintf(stderr, "\t-a <ack-delay>        is the delay to an ACK (RTT)\n");
496   fprintf(stderr, "\t-b <bytes-to-send>    is the bytes to send on connection\n");
497   fprintf(stderr, "\t-i <ip-addr-hex>      is the sender IP address in hex\n");
498   fprintf(stderr, "\t-I <ip-addr-hex>      is the recipient IP address in hex\n");
499   fprintf(stderr, "\t-n <ISN>              is almost the ISN for the sender\n");
500   fprintf(stderr, "\t-N <ISN>              is almost the ISN for the recipient\n");
501   fprintf(stderr, "\t-p <port-number-hex>  is the port number for sender\n");
502   fprintf(stderr, "\t-P <port-number-hex>  is the port number for recipient\n");
503   fprintf(stderr, "\t-s <send-spacing>     is the send spacing\n");
504   fprintf(stderr, "\t-w <window-size>      is the window size\n");
505 }
506
507 int
508 all_digits(char *str)
509 {
510   int i;
511   if (!str || !(*str)) {
512     return 0;
513   }
514    
515   for (i = 0; str[i]; i++) {
516     if (!isdigit(str[i]))
517       return 0;
518   }
519
520   return 1;
521 }
522
523 /*
524  * Process a list of drops. These are of the form:
525  *
526  * first_seg,seg_count[,first_seg,seg_count]*
527  */
528 void 
529 process_drop_list(char *drop_list)
530 {
531   int commas=0, i;
532   char *tok, *save;
533
534   if (!drop_list || !(*drop_list)) {
535     fprintf(stderr, "Strange drop list. NULL or an empty string. No drops!\n");
536     return;
537   }
538   save = (char *)g_strdup(drop_list);
539
540   for (tok=(char *)strtok(drop_list, ","); tok; tok=(char *)strtok(NULL, ",")) {
541     commas++;
542   }
543
544   /* Now, we have commas, divide by two and round up */
545
546   seg_drop_count = (commas+1)/2;
547   drops = (seg_drop_t *)g_malloc(sizeof(seg_drop_t) * seg_drop_count);
548   if (!drops) {
549     fprintf(stderr, "Unable to allocate space for drops ... going without!\n");
550     seg_drop_count = 0;
551     g_free(save);
552     return;
553   }
554
555   /* Now, go through the list again and build the drop list. Any errors and */
556   /* we abort and print a usage message                                     */
557
558   commas = 0;
559   for (tok=(char *)strtok(save, ","); tok; tok=(char *)strtok(NULL, ",")) {
560     int num = atoi(tok);
561
562     if (!all_digits(tok)) {
563       fprintf(stderr, "Error in segment offset or count. Not all digits: %s\n",
564               tok);
565       fprintf(stderr, "No packet drops being performed!\n");
566       g_free(save);
567       g_free(drops);
568       seg_drop_count = 0; drops = NULL;
569       return;
570     }
571     if (num == 0) num = 1;
572     if (commas % 2) 
573       drops[commas / 2].drop_seg_count = num;
574     else
575       drops[commas / 2].drop_seg_start = num;
576   }
577
578   
579
580 }
581
582 int 
583 main(int argc, char *argv[])
584 {
585         int i;
586         int len;
587         int type;
588         int cnt;
589         extern char *optarg;
590         extern int optind;
591         int opt;
592
593         while ((opt = getopt(argc, argv, "a:b:d:Di:I:j:l:n:N:p:P:r:s:vw:")) != EOF) {
594           switch (opt) {
595           case 'a':
596             ack_delay = atoi(optarg);
597             break;
598
599           case 'b': /* Bytes ... */
600             total_bytes = atoi(optarg);
601             break;
602
603           case 'd': /* A list of drops to simulate */
604             process_drop_list(optarg);
605             break;
606
607           case 'D': /* Toggle tcp_nodelay */
608             tcp_nodelay = (tcp_nodelay + 1) % 1;
609             break;
610
611           case 'i':
612             ip_1 = optarg;
613             break;
614
615           case 'I':
616             ip_2 = optarg;
617             break;
618
619           case 'l':
620             snap_len = atoi(optarg);
621             break;
622
623           case 'n': /* ISN for send dirn, ie, seq_1 */
624             seq_1 = atoi(optarg);
625             break;
626
627           case 'N': /* ISN for recv dirn, ie, seq_2 */
628             seq_2 = atoi(optarg);
629             break;
630
631           case 'p':
632             port_1 = optarg;
633             break;
634
635           case 'P':
636             port_2 = optarg;
637             break;
638
639           case 'r':
640             run_type = atoi(optarg);
641             break;
642
643           case 's':
644             send_spacing = atoi(optarg);
645             break;
646
647           case 'v':
648             verbose++;
649             break;
650
651           case 'w':  /* Window ... */
652             window = atoi(optarg);
653             ssthresh = window / 2;   /* Have to recalc this ... */
654             break;
655
656           default:
657             usage();
658             break;
659           }
660         }
661
662         if (verbose) fprintf(stderr, "IP1: %s, IP2: %s, P1: %s, P2: %s, Ack Delay: %d, Send Spacing: %d\n",
663                 ip_1, ip_2, port_1, port_2, ack_delay, send_spacing);
664
665         /*return 0; */
666
667         if (run_type == 0) {
668           makeackedrun(total_bytes, send_spacing, ack_delay);
669         }
670         else {
671           for(cnt=0;cnt<200;cnt++){
672             type=rand()%150;
673             if(type<75){
674               int j;
675               j=5+rand()%10;
676               for(i=0;i<j;i++){
677                 makeackedrun(32768, send_spacing, ack_delay);
678               }
679             } else if(type<90) {
680               int j;
681               j=4+rand()%4;
682               for(i=0;i<j;i++){
683                 len=100+rand()&0xfff;
684                 makeackedrun(len, send_spacing, ack_delay);
685               }
686             } else {
687               for(i=0;i<5;i++){
688                 len=100+rand()&0x3fff+0x1fff;
689                 makeackedrun(len, send_spacing, ack_delay);
690                 /*makeackedrundroppedtail8kb(len, send_spacing, ack_delay);*/
691               }
692             }
693           }
694         }
695         return 0;
696 }
697