aboutsummaryrefslogtreecommitdiff
path: root/networking/tftp.c
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2007-05-08 23:12:21 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2007-05-08 23:12:21 +0000
commita04561f5f7b6f1975c1bded6f11001f03190058c (patch)
treebe7dbf6c0cfe0032938c8d2151662b652db87e5f /networking/tftp.c
parent7e84e539de530b2060f0e570fc8f063ed0aaad2f (diff)
downloadbusybox-w32-a04561f5f7b6f1975c1bded6f11001f03190058c.tar.gz
busybox-w32-a04561f5f7b6f1975c1bded6f11001f03190058c.tar.bz2
busybox-w32-a04561f5f7b6f1975c1bded6f11001f03190058c.zip
tftp: code diet, and I think retransmits were broken.
function old new delta static.errcode_str - 32 +32 tftp_main 359 345 -14 tftp_bb_error_msg 32 - -32 .rodata 130931 130899 -32 tftp 1720 1558 -162 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 0/3 up/down: 32/-240) Total: -208 bytes
Diffstat (limited to 'networking/tftp.c')
-rw-r--r--networking/tftp.c368
1 files changed, 159 insertions, 209 deletions
diff --git a/networking/tftp.c b/networking/tftp.c
index 0621dde69..1f1dfff71 100644
--- a/networking/tftp.c
+++ b/networking/tftp.c
@@ -36,17 +36,6 @@
36#define TFTP_ERROR 5 36#define TFTP_ERROR 5
37#define TFTP_OACK 6 37#define TFTP_OACK 6
38 38
39static const char *const tftp_bb_error_msg[] = {
40 "Undefined error",
41 "File not found",
42 "Access violation",
43 "Disk full or allocation error",
44 "Illegal TFTP operation",
45 "Unknown transfer ID",
46 "File already exists",
47 "No such user"
48};
49
50#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT 39#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
51#define USE_GETPUT(a) 40#define USE_GETPUT(a)
52#define CMD_GET(cmd) 1 41#define CMD_GET(cmd) 1
@@ -62,7 +51,7 @@ static const char *const tftp_bb_error_msg[] = {
62#define CMD_PUT(cmd) ((cmd) & 2) 51#define CMD_PUT(cmd) ((cmd) & 2)
63#endif 52#endif
64/* NB: in the code below 53/* NB: in the code below
65 * CMD_GET(cmd) and CMD_GET(cmd) are mutually exclusive 54 * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive
66 */ 55 */
67 56
68 57
@@ -86,7 +75,7 @@ static int tftp_blocksize_check(int blocksize, int bufsize)
86 return blocksize; 75 return blocksize;
87} 76}
88 77
89static char *tftp_option_get(char *buf, int len, const char * const option) 78static char *tftp_option_get(char *buf, int len, const char *option)
90{ 79{
91 int opt_val = 0; 80 int opt_val = 0;
92 int opt_found = 0; 81 int opt_found = 0;
@@ -94,32 +83,24 @@ static char *tftp_option_get(char *buf, int len, const char * const option)
94 83
95 while (len > 0) { 84 while (len > 0) {
96 /* Make sure the options are terminated correctly */ 85 /* Make sure the options are terminated correctly */
97
98 for (k = 0; k < len; k++) { 86 for (k = 0; k < len; k++) {
99 if (buf[k] == '\0') { 87 if (buf[k] == '\0') {
100 break; 88 goto nul_found;
101 } 89 }
102 } 90 }
103 91 return NULL;
104 if (k >= len) { 92 nul_found:
105 break;
106 }
107
108 if (opt_val == 0) { 93 if (opt_val == 0) {
109 if (strcasecmp(buf, option) == 0) { 94 if (strcasecmp(buf, option) == 0) {
110 opt_found = 1; 95 opt_found = 1;
111 } 96 }
112 } else { 97 } else if (opt_found) {
113 if (opt_found) { 98 return buf;
114 return buf;
115 }
116 } 99 }
117 100
118 k++; 101 k++;
119
120 buf += k; 102 buf += k;
121 len -= k; 103 len -= k;
122
123 opt_val ^= 1; 104 opt_val ^= 1;
124 } 105 }
125 106
@@ -140,15 +121,15 @@ static int tftp(
140 fd_set rfds; 121 fd_set rfds;
141 int socketfd; 122 int socketfd;
142 int len; 123 int len;
143 int opcode = 0; 124 int send_len;
144 int finished = 0; 125 USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;)
145 int timeout = TFTP_NUM_RETRIES; 126 smallint finished = 0;
127 uint16_t opcode;
146 uint16_t block_nr = 1; 128 uint16_t block_nr = 1;
147 uint16_t tmp; 129 uint16_t recv_blk;
130 int timeout = TFTP_NUM_RETRIES;
148 char *cp; 131 char *cp;
149 132
150 USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;)
151
152 unsigned org_port; 133 unsigned org_port;
153 len_and_sockaddr *const from = alloca(offsetof(len_and_sockaddr, sa) + peer_lsa->len); 134 len_and_sockaddr *const from = alloca(offsetof(len_and_sockaddr, sa) + peer_lsa->len);
154 135
@@ -168,109 +149,83 @@ static int tftp(
168 if (CMD_GET(cmd)) { 149 if (CMD_GET(cmd)) {
169 opcode = TFTP_RRQ; 150 opcode = TFTP_RRQ;
170 } 151 }
171 152 cp = xbuf + 2;
172 while (1) { 153 /* add filename and mode */
173 cp = xbuf; 154 /* fill in packet if the filename fits into xbuf */
174 155 len = strlen(remotefile) + 1;
175 /* first create the opcode part */ 156 if (2 + len + sizeof("octet") >= tftp_bufsize) {
176 /* (this 16bit store is aligned) */ 157 bb_error_msg("remote filename is too long");
177 *((uint16_t*)cp) = htons(opcode); 158 goto ret;
178 cp += 2; 159 }
179 160 strcpy(cp, remotefile);
180 /* add filename and mode */ 161 cp += len;
181 if (CMD_GET(cmd) ? (opcode == TFTP_RRQ) : (opcode == TFTP_WRQ)) { 162 /* add "mode" part of the package */
182 int too_long = 0; 163 strcpy(cp, "octet");
183 164 cp += sizeof("octet");
184 /* see if the filename fits into xbuf
185 * and fill in packet. */
186 len = strlen(remotefile) + 1;
187
188 if ((cp + len) >= &xbuf[tftp_bufsize - 1]) {
189 too_long = 1;
190 } else {
191 safe_strncpy(cp, remotefile, len);
192 cp += len;
193 }
194
195 if (too_long || (&xbuf[tftp_bufsize - 1] - cp) < sizeof("octet")) {
196 bb_error_msg("remote filename too long");
197 break;
198 }
199
200 /* add "mode" part of the package */
201 memcpy(cp, "octet", sizeof("octet"));
202 cp += sizeof("octet");
203 165
204#if ENABLE_FEATURE_TFTP_BLOCKSIZE 166#if ENABLE_FEATURE_TFTP_BLOCKSIZE
205 167 len = tftp_bufsize - 4; /* data block size */
206 len = tftp_bufsize - 4; /* data block size */ 168 if (len != TFTP_BLOCKSIZE_DEFAULT) {
207 169 /* rfc2348 says that 65464 is a max allowed value */
208 if (len != TFTP_BLOCKSIZE_DEFAULT) { 170 if ((&xbuf[tftp_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
209 171 bb_error_msg("remote filename is too long");
210 if ((&xbuf[tftp_bufsize - 1] - cp) < 15) { 172 goto ret;
211 bb_error_msg("remote filename too long");
212 break;
213 }
214
215 /* add "blksize" + number of blocks */
216 memcpy(cp, "blksize", sizeof("blksize"));
217 cp += sizeof("blksize");
218 cp += snprintf(cp, 6, "%d", len) + 1;
219
220 want_option_ack = 1;
221 }
222#endif
223 } 173 }
174 /* add "blksize", <nul>, blocksize */
175 strcpy(cp, "blksize");
176 cp += sizeof("blksize");
177 cp += snprintf(cp, 6, "%d", len) + 1;
178 want_option_ack = 1;
179 }
180#endif
181 /* First packet is built, so skip packet generation */
182 goto send_pkt;
224 183
225 /* add ack and data */ 184 while (1) {
226 185 /* Build ACK or DATA */
227 if (CMD_GET(cmd) ? (opcode == TFTP_ACK) : (opcode == TFTP_DATA)) { 186 cp = xbuf + 2;
228 /* TODO: unaligned access! */ 187 *((uint16_t*)cp) = htons(block_nr);
229 *((uint16_t*)cp) = htons(block_nr); 188 cp += 2;
230 cp += 2; 189 block_nr++;
231 block_nr++; 190 opcode = TFTP_ACK;
232 191 if (CMD_PUT(cmd)) {
233 if (CMD_PUT(cmd) && (opcode == TFTP_DATA)) { 192 opcode = TFTP_DATA;
234 len = full_read(localfd, cp, tftp_bufsize - 4); 193 len = full_read(localfd, cp, tftp_bufsize - 4);
235 194 if (len < 0) {
236 if (len < 0) { 195 bb_perror_msg(bb_msg_read_error);
237 bb_perror_msg(bb_msg_read_error); 196 goto ret;
238 break; 197 }
239 } 198 if (len != (tftp_bufsize - 4)) {
240 199 finished = 1;
241 if (len != (tftp_bufsize - 4)) {
242 finished++;
243 }
244
245 cp += len;
246 } 200 }
201 cp += len;
247 } 202 }
248 203 send_pkt:
249 /* send packet */ 204 /* Send packet */
250 205 *((uint16_t*)xbuf) = htons(opcode); /* fill in opcode part */
251 timeout = TFTP_NUM_RETRIES; /* re-initialize */ 206 timeout = TFTP_NUM_RETRIES; /* re-initialize */
252 do { 207 while (1) {
253 len = cp - xbuf; 208 send_len = cp - xbuf;
209 /* nb: need to preserve send_len value in code below
210 * for potential resend! */
211 send_again:
254#if ENABLE_DEBUG_TFTP 212#if ENABLE_DEBUG_TFTP
255 fprintf(stderr, "sending %u bytes\n", len); 213 fprintf(stderr, "sending %u bytes\n", send_len);
256 for (cp = xbuf; cp < &xbuf[len]; cp++) 214 for (cp = xbuf; cp < &xbuf[send_len]; cp++)
257 fprintf(stderr, "%02x ", (unsigned char) *cp); 215 fprintf(stderr, "%02x ", (unsigned char) *cp);
258 fprintf(stderr, "\n"); 216 fprintf(stderr, "\n");
259#endif 217#endif
260 xsendto(socketfd, xbuf, len, &peer_lsa->sa, peer_lsa->len); 218 xsendto(socketfd, xbuf, send_len, &peer_lsa->sa, peer_lsa->len);
219 /* Was it final ACK? then exit */
220 if (finished && (opcode == TFTP_ACK))
221 goto ret;
261 222
262 if (finished && (opcode == TFTP_ACK)) { 223 /* Receive packet */
263 break;
264 }
265
266 /* receive packet */
267 recv_again: 224 recv_again:
268 tv.tv_sec = TFTP_TIMEOUT; 225 tv.tv_sec = TFTP_TIMEOUT;
269 tv.tv_usec = 0; 226 tv.tv_usec = 0;
270
271 FD_ZERO(&rfds); 227 FD_ZERO(&rfds);
272 FD_SET(socketfd, &rfds); 228 FD_SET(socketfd, &rfds);
273
274 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) { 229 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
275 unsigned from_port; 230 unsigned from_port;
276 case 1: 231 case 1:
@@ -280,7 +235,7 @@ static int tftp(
280 &from->sa, &from->len); 235 &from->sa, &from->len);
281 if (len < 0) { 236 if (len < 0) {
282 bb_perror_msg("recvfrom"); 237 bb_perror_msg("recvfrom");
283 break; 238 goto ret;
284 } 239 }
285 from_port = get_nport(&from->sa); 240 from_port = get_nport(&from->sa);
286 if (port == org_port) { 241 if (port == org_port) {
@@ -292,57 +247,57 @@ static int tftp(
292 } 247 }
293 if (port != from_port) 248 if (port != from_port)
294 goto recv_again; 249 goto recv_again;
295 timeout = 0; 250 goto recvd_good;
296 break;
297 case 0: 251 case 0:
298 bb_error_msg("timeout");
299 timeout--; 252 timeout--;
300 if (timeout == 0) { 253 if (timeout == 0) {
301 len = -1;
302 bb_error_msg("last timeout"); 254 bb_error_msg("last timeout");
255 goto ret;
303 } 256 }
304 break; 257 bb_error_msg("last timeout" + 5);
258 goto send_again; /* resend last sent pkt */
305 default: 259 default:
306 bb_perror_msg("select"); 260 bb_perror_msg("select");
307 len = -1; 261 goto ret;
308 } 262 }
263 } /* while we don't see recv packet with correct port# */
309 264
310 } while (timeout && (len >= 0)); 265 /* Process recv'ed packet */
311 266 recvd_good:
312 if (finished || (len < 0)) {
313 break;
314 }
315
316 /* process received packet */
317 /* (both accesses seems to be aligned) */
318
319 opcode = ntohs( ((uint16_t*)rbuf)[0] ); 267 opcode = ntohs( ((uint16_t*)rbuf)[0] );
320 tmp = ntohs( ((uint16_t*)rbuf)[1] ); 268 recv_blk = ntohs( ((uint16_t*)rbuf)[1] );
321 269
322#if ENABLE_DEBUG_TFTP 270#if ENABLE_DEBUG_TFTP
323 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp); 271 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk);
324#endif 272#endif
325 273
326 if (opcode == TFTP_ERROR) { 274 if (opcode == TFTP_ERROR) {
327 const char *msg = NULL; 275 static const char *const errcode_str[] = {
276 "",
277 "file not found",
278 "access violation",
279 "disk full",
280 "illegal TFTP operation",
281 "unknown transfer id",
282 "file already exists",
283 "no such user",
284 };
285 enum { NUM_ERRCODE = sizeof(errcode_str) / sizeof(errcode_str[0]) };
286
287 const char *msg = "";
328 288
329 if (rbuf[4] != '\0') { 289 if (rbuf[4] != '\0') {
330 msg = &rbuf[4]; 290 msg = &rbuf[4];
331 rbuf[tftp_bufsize - 1] = '\0'; 291 rbuf[tftp_bufsize - 1] = '\0';
332 } else if (tmp < (sizeof(tftp_bb_error_msg) 292 } else if (recv_blk < NUM_ERRCODE) {
333 / sizeof(char *))) { 293 msg = errcode_str[recv_blk];
334 msg = tftp_bb_error_msg[tmp];
335 }
336
337 if (msg) {
338 bb_error_msg("server says: %s", msg);
339 } 294 }
340 295 bb_error_msg("server error: (%u) %s", recv_blk, msg);
341 break; 296 goto ret;
342 } 297 }
298
343#if ENABLE_FEATURE_TFTP_BLOCKSIZE 299#if ENABLE_FEATURE_TFTP_BLOCKSIZE
344 if (want_option_ack) { 300 if (want_option_ack) {
345
346 want_option_ack = 0; 301 want_option_ack = 0;
347 302
348 if (opcode == TFTP_OACK) { 303 if (opcode == TFTP_OACK) {
@@ -350,87 +305,81 @@ static int tftp(
350 char *res; 305 char *res;
351 306
352 res = tftp_option_get(&rbuf[2], len - 2, "blksize"); 307 res = tftp_option_get(&rbuf[2], len - 2, "blksize");
353
354 if (res) { 308 if (res) {
355 int blksize = xatoi_u(res); 309 int blksize = xatoi_u(res);
356 310 if (!tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
357 if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) { 311 bb_error_msg("server proposes bad blksize %d, exiting", blksize);
358 if (CMD_PUT(cmd)) { 312 // FIXME: must also send ERROR 8 to server...
359 opcode = TFTP_DATA; 313 goto ret;
360 } else { 314 }
361 opcode = TFTP_ACK;
362 }
363#if ENABLE_DEBUG_TFTP 315#if ENABLE_DEBUG_TFTP
364 fprintf(stderr, "using blksize %u\n", 316 fprintf(stderr, "using blksize %u\n",
365 blksize); 317 blksize);
366#endif 318#endif
367 tftp_bufsize = blksize + 4; 319 tftp_bufsize = blksize + 4;
368 block_nr = 0; 320 block_nr = 0; // TODO: explain why???
369 continue; 321 continue;
370 }
371 } 322 }
372 /* FIXME: 323 /* rfc2347:
373 * we should send ERROR 8 */ 324 * "An option not acknowledged by the server
374 bb_error_msg("bad server option"); 325 * must be ignored by the client and server
375 break; 326 * as if it were never requested." */
376 } 327 }
377 328
378 bb_error_msg("warning: blksize not supported by server" 329 bb_error_msg("blksize is not supported by server"
379 " - reverting to 512"); 330 " - reverting to 512");
380
381 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4; 331 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
382 } 332 }
383#endif 333#endif
334 /* block_nr is already advanced to next block# we expect
335 * to get / block# we are about to send next time */
384 336
385 if (CMD_GET(cmd) && (opcode == TFTP_DATA)) { 337 if (CMD_GET(cmd) && (opcode == TFTP_DATA)) {
386 if (tmp == block_nr) { 338 if (recv_blk == block_nr) {
387 len = full_write(localfd, &rbuf[4], len - 4); 339 len = full_write(localfd, &rbuf[4], len - 4);
388
389 if (len < 0) { 340 if (len < 0) {
390 bb_perror_msg(bb_msg_write_error); 341 bb_perror_msg(bb_msg_write_error);
391 break; 342 goto ret;
392 } 343 }
393
394 if (len != (tftp_bufsize - 4)) { 344 if (len != (tftp_bufsize - 4)) {
395 finished++; 345 finished = 1;
396 } 346 }
397 347 continue; /* send ACK */
398 opcode = TFTP_ACK;
399 continue;
400 } 348 }
401 /* in case the last ack disappeared into the ether */ 349 if (recv_blk == (block_nr - 1)) {
402 if (tmp == (block_nr - 1)) {
403 --block_nr;
404 opcode = TFTP_ACK;
405 continue;
406// tmp==(block_nr-1) and (tmp+1)==block_nr is always same, I think. wtf?
407 } else if (tmp + 1 == block_nr) {
408 /* Server lost our TFTP_ACK. Resend it */ 350 /* Server lost our TFTP_ACK. Resend it */
409 block_nr = tmp; 351 block_nr = recv_blk;
410 opcode = TFTP_ACK;
411 continue; 352 continue;
412 } 353 }
413 } 354 }
414 355
415 if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) { 356 if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) {
416 if (tmp == (uint16_t) (block_nr - 1)) { 357 /* did server ACK our last DATA pkt? */
417 if (finished) { 358 if (recv_blk == (uint16_t) (block_nr - 1)) {
418 break; 359 if (finished)
419 } 360 goto ret;
420 361 continue; /* send next block */
421 opcode = TFTP_DATA;
422 continue;
423 } 362 }
424 } 363 }
364 /* Awww... recv'd packet is not recognized! */
365 goto recv_again;
366 /* why recv_again? - rfc1123 says:
367 * "The sender (i.e., the side originating the DATA packets)
368 * must never resend the current DATA packet on receipt
369 * of a duplicate ACK".
370 * DATA pkts are resent ONLY on timeout.
371 * Thus "goto send_again" will ba a bad mistake above.
372 * See:
373 * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome
374 */
425 } 375 }
426 376 ret:
427 if (ENABLE_FEATURE_CLEAN_UP) { 377 if (ENABLE_FEATURE_CLEAN_UP) {
428 close(socketfd); 378 close(socketfd);
429 free(xbuf); 379 free(xbuf);
430 free(rbuf); 380 free(rbuf);
431 } 381 }
432 382 return finished == 0; /* returns 1 on failure */
433 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
434} 383}
435 384
436int tftp_main(int argc, char **argv); 385int tftp_main(int argc, char **argv);
@@ -458,6 +407,7 @@ int tftp_main(int argc, char **argv)
458 "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"), 407 "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
459 &localfile, &remotefile 408 &localfile, &remotefile
460 USE_FEATURE_TFTP_BLOCKSIZE(, &sblocksize)); 409 USE_FEATURE_TFTP_BLOCKSIZE(, &sblocksize));
410 argv += optind;
461 411
462 flags = O_RDONLY; 412 flags = O_RDONLY;
463 if (CMD_GET(cmd)) 413 if (CMD_GET(cmd))
@@ -472,25 +422,26 @@ int tftp_main(int argc, char **argv)
472 } 422 }
473#endif 423#endif
474 424
475 if (localfile == NULL) 425 if (!localfile)
476 localfile = remotefile; 426 localfile = remotefile;
477 if (remotefile == NULL) 427 if (!remotefile)
478 remotefile = localfile; 428 remotefile = localfile;
479 if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL)) 429 /* Error if filename or host is not known */
430 if (!remotefile || !argv[0])
480 bb_show_usage(); 431 bb_show_usage();
481 432
482 if (localfile == NULL || LONE_DASH(localfile)) { 433 if (LONE_DASH(localfile)) {
483 fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO; 434 fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
484 } else { 435 } else {
485 fd = xopen3(localfile, flags, 0644); 436 fd = xopen(localfile, flags);
486 } 437 }
487 438
488 port = bb_lookup_port(argv[optind + 1], "udp", 69); 439 port = bb_lookup_port(argv[1], "udp", 69);
489 peer_lsa = xhost2sockaddr(argv[optind], port); 440 peer_lsa = xhost2sockaddr(argv[0], port);
490 441
491#if ENABLE_DEBUG_TFTP 442#if ENABLE_DEBUG_TFTP
492 fprintf(stderr, "using server \"%s\", " 443 fprintf(stderr, "using server \"%s\", "
493 "remotefile \"%s\", localfile \"%s\".\n", 444 "remotefile \"%s\", localfile \"%s\"\n",
494 xmalloc_sockaddr2dotted(&peer_lsa->sa, peer_lsa->len), 445 xmalloc_sockaddr2dotted(&peer_lsa->sa, peer_lsa->len),
495 remotefile, localfile); 446 remotefile, localfile);
496#endif 447#endif
@@ -501,11 +452,10 @@ int tftp_main(int argc, char **argv)
501#endif 452#endif
502 peer_lsa, remotefile, fd, port, blocksize); 453 peer_lsa, remotefile, fd, port, blocksize);
503 454
504 if (fd > 1) { 455 if (ENABLE_FEATURE_CLEAN_UP)
505 if (ENABLE_FEATURE_CLEAN_UP) 456 close(fd);
506 close(fd); 457 if (result != EXIT_SUCCESS && !LONE_DASH(localfile) && CMD_GET(cmd)) {
507 if (CMD_GET(cmd) && result != EXIT_SUCCESS) 458 unlink(localfile);
508 unlink(localfile);
509 } 459 }
510 return result; 460 return result;
511} 461}