-fix threepeer test
[oweals/gnunet.git] / src / gns / test_gns_dht_three_peers.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file gns/test_gns_dht_threepeer.c
22  * @brief tests dht lookup over 3 peers
23  *
24  * topology:
25  * alice <----> bob <-----> dave
26  *
27  * alice queries for www.buddy.bob.gnunet
28  *
29  */
30 #include "platform.h"
31 #include "gnunet_common.h"
32 #include "gnunet_disk_lib.h"
33 #include "gnunet_testing_lib-new.h"
34 #include "gnunet_testbed_service.h"
35 #include "gnunet_core_service.h"
36 #include "gnunet_dht_service.h"
37 #include "block_dns.h"
38 #include "gnunet_signatures.h"
39 #include "gnunet_namestore_service.h"
40 #include "gnunet_dnsparser_lib.h"
41 #include "gnunet_gns_service.h"
42
43 #define ZONE_PUT_WAIT_TIME GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 10)
44
45 /* If number of peers not in config file, use this number */
46 #define DEFAULT_NUM_PEERS 2
47
48 #define TEST_DOMAIN "www.buddy.bob.gnunet"
49 #define TEST_IP "1.1.1.1"
50 #define TEST_DAVE_PSEU "hagbard"
51 #define TEST_NUM_PEERS 3
52 #define TEST_NUM_CON 3
53
54
55 /* Timeout for entire testcase */
56 #define TIMEOUT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 30)
57
58 /* Global return value (0 for success, anything else for failure) */
59 static int ok;
60
61 /* Task handle to use to schedule test failure */
62 GNUNET_SCHEDULER_TaskIdentifier die_task;
63 GNUNET_SCHEDULER_TaskIdentifier wait_task;
64
65 struct GNUNET_CRYPTO_ShortHashCode dave_hash;
66
67 struct GNUNET_CRYPTO_ShortHashCode bob_hash;
68
69 struct GNUNET_TESTBED_Peer **cpeers;
70
71 struct GNUNET_GNS_Handle *gh;
72 struct GNUNET_GNS_LookupRequest *lookup_handle;
73
74 struct GNUNET_TESTBED_Operation *get_cfg_ops[3];
75 struct GNUNET_TESTBED_Operation *connect_ops[3];
76 struct GNUNET_CONFIGURATION_Handle *cfg_handles[3];
77 struct GNUNET_NAMESTORE_Handle *nh[3];
78
79 static int dave_is_setup;
80 static int bob_is_setup;
81 static int alice_is_setup;
82
83 /**
84  * Check if the get_handle is being used, if so stop the request.  Either
85  * way, schedule the end_badly_cont function which actually shuts down the
86  * test.
87  */
88 static void
89 end_badly (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
90 {
91   die_task = GNUNET_SCHEDULER_NO_TASK;
92   int c;
93
94   if (GNUNET_SCHEDULER_NO_TASK != wait_task)
95   {
96       GNUNET_SCHEDULER_cancel (wait_task);
97       wait_task = GNUNET_SCHEDULER_NO_TASK;
98   }
99
100   for (c = 0; c < 3; c++)
101   {
102     if (NULL != nh[c])
103     {
104       GNUNET_NAMESTORE_disconnect(nh[c]);
105       nh[c] = NULL;
106     }
107
108     if (NULL != get_cfg_ops[c])
109     {
110         GNUNET_TESTBED_operation_cancel(get_cfg_ops[c]);
111         get_cfg_ops[c] = NULL;
112     }
113     if (NULL != connect_ops[c])
114     {
115         GNUNET_TESTBED_operation_cancel(connect_ops[c]);
116         connect_ops[c] = NULL;
117     }
118     if (NULL != cfg_handles[c])
119     {
120       GNUNET_CONFIGURATION_destroy (cfg_handles[c]);
121       cfg_handles[c] = NULL;
122     }
123   }
124   
125   if (NULL != lookup_handle)
126   {
127     GNUNET_GNS_cancel_lookup_request (lookup_handle);
128     lookup_handle = NULL;
129   }
130   if (NULL != gh)
131   {
132     GNUNET_GNS_disconnect(gh);
133     gh = NULL;
134   }
135   
136   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Test failed \n");
137   GNUNET_break (0);
138   GNUNET_SCHEDULER_shutdown ();
139   ok = 1;
140 }
141
142 static void
143 end (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
144 {
145   int c;
146   if (GNUNET_SCHEDULER_NO_TASK != die_task)
147   {
148       GNUNET_SCHEDULER_cancel (die_task);
149       die_task = GNUNET_SCHEDULER_NO_TASK;
150   }
151
152   for (c = 0; c < 3; c++)
153   {
154     if (NULL != nh[c])
155     {
156       GNUNET_NAMESTORE_disconnect(nh[c]);
157       nh[c] = NULL;
158     }
159     if (NULL != cfg_handles[c])
160     {
161       GNUNET_CONFIGURATION_destroy (cfg_handles[c]);
162       cfg_handles[c] = NULL;
163     }
164   }
165
166   if (NULL != gh)
167   {
168     GNUNET_GNS_disconnect(gh);
169     gh = NULL;
170   }
171
172   if (0 == ok)
173     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Test ended successful\n");
174   else
175     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Test failed\n");
176   GNUNET_SCHEDULER_shutdown ();
177 }
178
179 static void
180 end_now ()
181 {
182   GNUNET_SCHEDULER_add_now (&end, NULL);
183 }
184
185
186 static void
187 disconnect_ns (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
188 {
189
190   GNUNET_NAMESTORE_disconnect (cls);
191   if (cls == nh[0])
192     nh[0] = NULL;
193   if (cls == nh[1])
194     nh[1] = NULL;
195   if (cls == nh[2])
196     nh[2] = NULL;
197 }
198
199
200 static void
201 cont_ns (void* cls, int32_t s, const char* emsg)
202 {
203   GNUNET_SCHEDULER_add_now (&disconnect_ns, cls);
204 }
205
206 static void
207 on_lookup_result(void *cls, uint32_t rd_count,
208                  const struct GNUNET_NAMESTORE_RecordData *rd)
209 {
210   int i;
211   char* string_val;
212
213   if (rd_count == 0)
214   {
215     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
216                 "Lookup failed!\n");
217     ok = 2;
218   }
219   else
220   {
221     ok = 1;
222     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "name: %s\n", (char*)cls);
223     for (i=0; i<rd_count; i++)
224     {
225       string_val = GNUNET_NAMESTORE_value_to_string(rd[i].record_type,
226                                                     rd[i].data,
227                                                     rd[i].data_size);
228       if (0 == strcmp(string_val, TEST_IP))
229       {
230         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
231                     "%s correctly resolved to %s!\n", TEST_DOMAIN, string_val);
232         ok = 0;
233       }
234     }
235   }
236   end_now ();
237 }
238
239 static void
240 commence_testing(void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
241 {
242   static int wait = 0;
243   wait++;
244   if ((ZONE_PUT_WAIT_TIME.rel_value / 1000) == wait)
245   {
246     fprintf (stderr, "\n");
247     wait_task = GNUNET_SCHEDULER_NO_TASK;
248     lookup_handle = GNUNET_GNS_lookup(gh, TEST_DOMAIN, GNUNET_GNS_RECORD_A,
249                       GNUNET_NO,
250                       NULL,
251                       &on_lookup_result, TEST_DOMAIN);
252     if (GNUNET_SCHEDULER_NO_TASK != die_task)
253       GNUNET_SCHEDULER_cancel(die_task);
254     die_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_badly, "from lookup");
255   }
256   else
257   {
258       fprintf (stderr, ".");
259       wait_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &commence_testing, NULL);
260   }
261 }
262
263 void
264 all_connected ()
265 {
266   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Created all connections! Waiting for PUTs\n");
267   wait_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &commence_testing, NULL);
268 }
269
270
271 static void connect_peers ()
272 {
273   static int started;
274   started ++;
275
276   if (3 == started)
277   {
278       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "All peers started\n");
279
280       connect_ops[0] = GNUNET_TESTBED_overlay_connect (NULL, NULL, NULL,
281                                                        cpeers[0],
282                                                        cpeers[1]);
283
284       connect_ops[1] = GNUNET_TESTBED_overlay_connect (NULL, NULL, NULL,
285                                                        cpeers[1],
286                                                        cpeers[2]);
287
288       connect_ops[2] = GNUNET_TESTBED_overlay_connect (NULL, NULL, NULL,
289                                                        cpeers[0],
290                                                        cpeers[2]);
291   }
292 }
293
294 static int
295 setup_dave (const struct GNUNET_CONFIGURATION_Handle * cfg)
296 {
297   char* keyfile;
298   struct GNUNET_CRYPTO_RsaPrivateKey *key;
299   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
300   struct in_addr *web;
301   struct GNUNET_NAMESTORE_RecordData rd;
302
303   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up dave\n");
304   GNUNET_assert (NULL != cfg);
305   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "gns",
306                                                             "ZONEKEY",
307                                                             &keyfile))
308   {
309     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to get key from cfg\n");
310     return GNUNET_SYSERR;
311   }
312
313   key = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
314   if (NULL == key)
315   {
316
317     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to get key from cfg\n");
318     GNUNET_free (keyfile);
319     return GNUNET_SYSERR;
320   }
321
322   nh[0] = GNUNET_NAMESTORE_connect (cfg_handles[0]);
323   if (NULL == nh[0])
324   {
325     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to connect to namestore\n");
326     GNUNET_CRYPTO_rsa_key_free (key);
327     GNUNET_free (keyfile);
328     return GNUNET_SYSERR;
329   }
330
331   GNUNET_CRYPTO_rsa_key_get_public (key, &pkey);
332   GNUNET_CRYPTO_short_hash(&pkey, sizeof(pkey), &dave_hash);
333
334   web = GNUNET_malloc(sizeof(struct in_addr));
335   GNUNET_assert(1 == inet_pton (AF_INET, TEST_IP, web));
336   rd.data_size = sizeof(struct in_addr);
337   rd.data = web;
338   rd.record_type = GNUNET_GNS_RECORD_A;
339   rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY;
340
341   GNUNET_NAMESTORE_record_create (nh[0], key, "www", &rd, NULL, NULL);
342
343   rd.data_size = strlen(TEST_DAVE_PSEU);
344   rd.data = TEST_DAVE_PSEU;
345   rd.record_type = GNUNET_GNS_RECORD_PSEU;
346
347   GNUNET_NAMESTORE_record_create (nh[0], key, "+", &rd, &cont_ns, nh[0]);
348
349   GNUNET_CRYPTO_rsa_key_free(key);
350   GNUNET_free(keyfile);
351   GNUNET_free(web);
352   dave_is_setup = GNUNET_YES;
353   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up dave done\n");
354   return GNUNET_OK;
355 }
356
357 static int
358 setup_bob (const struct GNUNET_CONFIGURATION_Handle * cfg)
359 {
360   char* keyfile;
361   struct GNUNET_CRYPTO_RsaPrivateKey *key;
362   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
363   struct GNUNET_NAMESTORE_RecordData rd;
364
365   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up bob\n");
366   GNUNET_assert (NULL != cfg);
367   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "gns",
368                                                             "ZONEKEY",
369                                                             &keyfile))
370   {
371     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to get key from cfg\n");
372     return GNUNET_SYSERR;
373   }
374
375   key = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
376   if (NULL == key)
377   {
378
379     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to get key from cfg\n");
380     GNUNET_free (keyfile);
381     return GNUNET_SYSERR;
382   }
383
384   nh[1] = GNUNET_NAMESTORE_connect (cfg);
385   if (NULL == nh[1])
386   {
387     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to connect to namestore\n");
388     GNUNET_CRYPTO_rsa_key_free (key);
389     GNUNET_free (keyfile);
390     return GNUNET_SYSERR;
391   }
392   
393   GNUNET_CRYPTO_rsa_key_get_public (key, &pkey);
394   GNUNET_CRYPTO_short_hash(&pkey, sizeof(pkey), &bob_hash);
395
396   rd.data_size = sizeof(struct GNUNET_CRYPTO_ShortHashCode);
397   rd.data = &dave_hash;
398   rd.record_type = GNUNET_GNS_RECORD_PKEY;
399   rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY;
400
401   GNUNET_NAMESTORE_record_create (nh[1], key, "buddy", &rd, &cont_ns, nh[1]);
402
403   GNUNET_CRYPTO_rsa_key_free(key);
404   GNUNET_free(keyfile);
405   bob_is_setup = GNUNET_YES;
406   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up bob done\n");
407   return GNUNET_OK;
408 }
409
410 static int
411 setup_alice (const struct GNUNET_CONFIGURATION_Handle * cfg)
412 {
413   char* keyfile;
414   struct GNUNET_CRYPTO_RsaPrivateKey *key;
415   struct GNUNET_NAMESTORE_RecordData rd;
416
417
418   GNUNET_assert (NULL != cfg);
419   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "gns",
420                                                             "ZONEKEY",
421                                                             &keyfile))
422   {
423     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to get key from cfg\n");
424     return GNUNET_SYSERR;
425   }
426
427   key = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
428   if (NULL == key)
429   {
430
431     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to get key from cfg\n");
432     GNUNET_free (keyfile);
433     return GNUNET_SYSERR;
434   }
435
436   nh[2] = GNUNET_NAMESTORE_connect (cfg_handles[2]);
437   if (NULL == nh[2])
438   {
439     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to connect to namestore\n");
440     GNUNET_CRYPTO_rsa_key_free (key);
441     GNUNET_free (keyfile);
442     return GNUNET_SYSERR;
443   }
444
445   rd.data_size = sizeof(struct GNUNET_CRYPTO_ShortHashCode);
446   rd.data = &bob_hash;
447   rd.record_type = GNUNET_GNS_RECORD_PKEY;
448   rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY;
449
450   GNUNET_NAMESTORE_record_create (nh[2], key, "bob", &rd, &cont_ns, nh[2]);
451
452   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up alice gns\n");
453   gh = GNUNET_GNS_connect (cfg_handles[2]);
454   if (NULL == gh)
455   {
456     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to connect to gns\n");
457     GNUNET_CRYPTO_rsa_key_free (key);
458     GNUNET_free (keyfile);
459     return GNUNET_SYSERR;
460   }
461
462   GNUNET_CRYPTO_rsa_key_free (key);
463   GNUNET_free (keyfile);
464   alice_is_setup = GNUNET_YES;
465   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up alice  done\n");
466   return GNUNET_OK;
467 }
468
469 static void
470 end_badly_now ()
471 {
472   if (GNUNET_SCHEDULER_NO_TASK != die_task)
473     GNUNET_SCHEDULER_cancel (die_task);
474   die_task = GNUNET_SCHEDULER_add_now (&end_badly, NULL);
475 }
476
477
478 /**
479  * Callback to be called when the requested peer information is available
480  *
481  * @param cb_cls the closure from GNUNET_TETSBED_peer_get_information()
482  * @param op the operation this callback corresponds to
483  * @param pinfo the result; will be NULL if the operation has failed
484  * @param emsg error message if the operation has failed; will be NULL if the
485  *          operation is successfull
486  */
487 static void 
488 peerinfo_cb (void *cb_cls, struct GNUNET_TESTBED_Operation *op,
489              const struct GNUNET_TESTBED_PeerInformation *pinfo,
490              const char *emsg)
491 {
492   int res;
493
494   GNUNET_assert (GNUNET_TESTBED_PIT_CONFIGURATION == pinfo->pit);
495   if (GNUNET_NO == dave_is_setup)
496     res = setup_dave (pinfo->result.cfg);
497   else if (GNUNET_NO == bob_is_setup)
498     res = setup_bob (pinfo->result.cfg);
499   else
500     res = setup_alice (pinfo->result.cfg);
501   
502   GNUNET_TESTBED_operation_done (op);
503   op = NULL;
504
505   if (GNUNET_SYSERR == res)
506   {
507     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup peer \n");
508     end_badly_now();
509   }
510   else
511     connect_peers ();
512
513   /*if (get_cfg_ops[0] == op)
514   {
515     GNUNET_assert (GNUNET_TESTBED_PIT_CONFIGURATION == pinfo->pit);
516     res = setup_dave (pinfo->result.cfg);
517     GNUNET_TESTBED_operation_done (get_cfg_ops[0]);
518     get_cfg_ops[0] = NULL;
519     if (GNUNET_SYSERR == res)
520     {
521       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup dave \n");
522       end_badly_now();
523     }
524     else
525     {
526       connect_peers ();
527     }
528   }
529   else if (get_cfg_ops[1] == op)
530   {
531     GNUNET_assert (GNUNET_TESTBED_PIT_CONFIGURATION == pinfo->pit);
532     res = setup_bob (pinfo->result.cfg);
533     GNUNET_TESTBED_operation_done (get_cfg_ops[1]);
534     get_cfg_ops[1] = NULL;
535     if (GNUNET_SYSERR == res)
536     {
537       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup dave \n");
538       end_badly_now();
539     }
540     else
541     {
542       connect_peers ();
543     }
544   }
545   else if (get_cfg_ops[2] == op)
546   {
547     GNUNET_assert (GNUNET_TESTBED_PIT_CONFIGURATION == pinfo->pit);
548     res = setup_alice (pinfo->result.cfg);
549     GNUNET_TESTBED_operation_done (get_cfg_ops[2]);
550     get_cfg_ops[2] = NULL;
551     if (GNUNET_SYSERR == res)
552     {
553       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup dave \n");
554       end_badly_now();
555     }
556     else
557     {
558       connect_peers ();
559     }
560   }*/
561 }
562
563
564 void testbed_master (void *cls,
565                      unsigned int num_peers,
566                      struct GNUNET_TESTBED_Peer **peers)
567 {
568   GNUNET_assert (NULL != peers);
569   cpeers = peers;
570
571   /* peer 0: dave */
572   GNUNET_assert (NULL != peers[0]);
573   get_cfg_ops[0] = GNUNET_TESTBED_peer_get_information (peers[0],
574                                                         GNUNET_TESTBED_PIT_CONFIGURATION,
575                                                         &peerinfo_cb, NULL);
576
577   /* peer 1: bob */
578   GNUNET_assert (NULL != peers[1]);
579   get_cfg_ops[1] = GNUNET_TESTBED_peer_get_information (peers[1],
580                                                         GNUNET_TESTBED_PIT_CONFIGURATION,
581                                                         &peerinfo_cb, NULL );
582
583   /* peer 2: alice */
584   GNUNET_assert (NULL != peers[2]);
585   get_cfg_ops[2] = GNUNET_TESTBED_peer_get_information (peers[2],
586                                                         GNUNET_TESTBED_PIT_CONFIGURATION,
587                                                         &peerinfo_cb, NULL);
588
589 }
590
591 void testbed_controller_cb (void *cls, const struct GNUNET_TESTBED_EventInformation *event)
592 {
593   static int connections = 0;
594
595   switch (event->type)
596   {
597     case GNUNET_TESTBED_ET_OPERATION_FINISHED:
598       /* This part will still be called when
599          GNUNET_TESTBED_peer_get_information() succeeds. However, the code is
600          now more relevant in operation completion callback */
601       break;
602     case GNUNET_TESTBED_ET_CONNECT:
603       connections ++;
604       if (connections == 3)
605       {
606           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "All peers connected\n");
607           GNUNET_TESTBED_operation_done (connect_ops[0]);
608           connect_ops[0] = NULL;
609           GNUNET_TESTBED_operation_done (connect_ops[1]);
610           connect_ops[1] = NULL;
611           GNUNET_TESTBED_operation_done (connect_ops[2]);
612           connect_ops[2] = NULL;
613           all_connected ();
614       }
615       break;
616     default:
617       /* whatever ... */
618       break;
619   }
620 }
621
622 int
623 main (int argc, char *argv[])
624 {
625   uint64_t event_mask;
626   ok = 0;
627   event_mask = 0;
628   event_mask |= (1LL << GNUNET_TESTBED_ET_CONNECT);
629   event_mask |= (1LL << GNUNET_TESTBED_ET_OPERATION_FINISHED);
630   GNUNET_TESTBED_test_run ("test_gns_dht_three_peers", "test_gns_dht_default.conf",
631                            3, event_mask,
632                            &testbed_controller_cb, NULL,
633                            &testbed_master, NULL);
634   if (GNUNET_SYSERR == ok)
635     return 1;
636   return 0;
637 }
638
639 /* end of test_gns_dht_three_peers.c */
640