1 /* This case tests digest authentication.
2  HA1 = MD5("name:NetX Duo HTTP demo:Placeholderpassword")
3      = 01bb2595c9221423951ee86f3573b465
4  HA2 = MD5("GET:/index.htm")
5      = d4b1da8c7955d2e98bc56ffc93003b44
6  Response = MD5("01bb2595c9221423951ee86f3573b465:
7                  nonce:
8                  00000001:0a4f113b:auth:
9                  d4b1da8c7955d2e98bc56ffc93003b44")
10  */
11 #include    "tx_api.h"
12 #include    "nx_api.h"
13 #include    "fx_api.h"
14 #include    "nx_web_http_client.h"
15 #include    "nx_web_http_server.h"
16 
17 extern void test_control_return(UINT);
18 
19 #if !defined(NX_DISABLE_IPV4) && defined(NX_WEB_HTTP_DIGEST_ENABLE) && !defined(NX_WEB_HTTP_KEEPALIVE_DISABLE)
20 
21 #include "http_digest_authentication.c"
22 
23 #define     DEMO_STACK_SIZE         4096
24 
25 /* Set up FileX and file memory resources. */
26 static CHAR             ram_disk_memory[4096];
27 static FX_MEDIA         ram_disk;
28 static UCHAR            media_memory[4096];
29 
30 static UCHAR            server_stack[16000];
31 
32 /* Define device drivers.  */
33 extern void _fx_ram_driver(FX_MEDIA *media_ptr);
34 extern void _nx_ram_network_driver_1024(NX_IP_DRIVER *driver_req_ptr);
35 
36 /* Set up the HTTP client global variables. */
37 
38 #define         CLIENT_PACKET_SIZE  (NX_WEB_HTTP_CLIENT_MIN_PACKET_SIZE * 2)
39 
40 static TX_THREAD           client_thread;
41 static NX_PACKET_POOL      client_pool;
42 static NX_IP               client_ip;
43 static UINT                error_counter;
44 
45 /* Set up the HTTP server global variables */
46 
47 #define         SERVER_PACKET_SIZE  (NX_WEB_HTTP_SERVER_MIN_PACKET_SIZE * 2)
48 
49 static NX_WEB_HTTP_SERVER  my_server;
50 static NX_PACKET_POOL      server_pool;
51 static TX_THREAD           server_thread;
52 static NX_IP               server_ip;
53 static NX_TCP_SOCKET       client_socket[NX_WEB_HTTP_SERVER_NONCE_MAX + 1];
54 #ifdef __PRODUCT_NETXDUO__
55 static NXD_ADDRESS     server_ip_address;
56 #else
57 static ULONG           server_ip_address;
58 #endif
59 
60 static void thread_client_entry(ULONG thread_input);
61 static void thread_server_entry(ULONG thread_input);
62 
63 #define HTTP_SERVER_ADDRESS  IP_ADDRESS(1,2,3,4)
64 #define HTTP_CLIENT_ADDRESS  IP_ADDRESS(1,2,3,5)
65 
66 
67 static TX_SEMAPHORE server_start;
68 static TX_SEMAPHORE client_stop;
69 
70 static UINT  authentication_check(NX_WEB_HTTP_SERVER *server_ptr, UINT request_type,
71                                   CHAR *resource, CHAR **name, CHAR **password, CHAR **realm);
72 extern UINT _nx_web_http_client_receive(NX_WEB_HTTP_CLIENT *client_ptr, NX_PACKET **packet_ptr, ULONG wait_option);
73 
74 static CHAR nonce_buffer[NX_WEB_HTTP_SERVER_NONCE_SIZE + 1];
75 static CHAR temp_nonce_buffer[NX_WEB_HTTP_SERVER_NONCE_SIZE + 1];
76 static CHAR response_buffer[32 + 1];
77 static NX_MD5 client_md5data;
78 
79 static char pkt[] = {
80 0x47, 0x45, 0x54, 0x20, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x20, /* GET /index.htm  */
81 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, /* HTTP/1.1.. */
82 };
83 
84 #ifdef CTEST
test_application_define(void * first_unused_memory)85 VOID test_application_define(void *first_unused_memory)
86 #else
87 void    netx_web_digest_authenticate_timeout_test_application_define(void *first_unused_memory)
88 #endif
89 {
90 CHAR    *pointer;
91 UINT    status;
92 
93 
94     error_counter = 0;
95 
96     /* Setup the working pointer.  */
97     pointer =  (CHAR *) first_unused_memory;
98 
99     /* Create a helper thread for the server. */
100     tx_thread_create(&server_thread, "HTTP Server thread", thread_server_entry, 0,
101                      pointer, DEMO_STACK_SIZE,
102                      NX_WEB_HTTP_SERVER_PRIORITY, NX_WEB_HTTP_SERVER_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);
103 
104     pointer =  pointer + DEMO_STACK_SIZE;
105 
106     /* Initialize the NetX system.  */
107     nx_system_initialize();
108 
109     /* Create the server packet pool.  */
110     status =  nx_packet_pool_create(&server_pool, "HTTP Server Packet Pool", SERVER_PACKET_SIZE,
111                                     pointer, SERVER_PACKET_SIZE*8);
112     pointer = pointer + SERVER_PACKET_SIZE * 8;
113     if (status)
114         error_counter++;
115 
116     /* Create an IP instance.  */
117     status = nx_ip_create(&server_ip, "HTTP Server IP", HTTP_SERVER_ADDRESS,
118                           0xFFFFFF00UL, &server_pool, _nx_ram_network_driver_1024,
119                           pointer, 4096, 1);
120     pointer =  pointer + 4096;
121     if (status)
122         error_counter++;
123 
124     /* Enable ARP and supply ARP cache memory for the server IP instance.  */
125     status = nx_arp_enable(&server_ip, (void *) pointer, 1024);
126     pointer = pointer + 1024;
127     if (status)
128         error_counter++;
129 
130 
131      /* Enable TCP traffic.  */
132     status = nx_tcp_enable(&server_ip);
133     if (status)
134         error_counter++;
135 
136     /* Create the HTTP Client thread. */
137     status = tx_thread_create(&client_thread, "HTTP Client", thread_client_entry, 0,
138                               pointer, DEMO_STACK_SIZE,
139                               NX_WEB_HTTP_SERVER_PRIORITY + 2, NX_WEB_HTTP_SERVER_PRIORITY + 2, TX_NO_TIME_SLICE, TX_AUTO_START);
140     pointer =  pointer + DEMO_STACK_SIZE;
141     if (status)
142         error_counter++;
143 
144     /* Create the Client packet pool.  */
145     status =  nx_packet_pool_create(&client_pool, "HTTP Client Packet Pool", CLIENT_PACKET_SIZE,
146                                     pointer, CLIENT_PACKET_SIZE*16);
147     pointer = pointer + CLIENT_PACKET_SIZE * 16;
148     if (status)
149         error_counter++;
150 
151     /* Create an IP instance.  */
152     status = nx_ip_create(&client_ip, "HTTP Client IP", HTTP_CLIENT_ADDRESS,
153                           0xFFFFFF00UL, &client_pool, _nx_ram_network_driver_1024,
154                           pointer, 2048, 1);
155     pointer =  pointer + 2048;
156     if (status)
157         error_counter++;
158 
159     status  = nx_arp_enable(&client_ip, (void *) pointer, 1024);
160     pointer =  pointer + 2048;
161     if (status)
162         error_counter++;
163 
164      /* Enable TCP traffic.  */
165     status = nx_tcp_enable(&client_ip);
166     if (status)
167         error_counter++;
168 
169     tx_semaphore_create(&server_start, "server start", 0);
170     tx_semaphore_create(&client_stop, "client stop", 0);
171 }
172 
thread_client_entry(ULONG thread_input)173 void thread_client_entry(ULONG thread_input)
174 {
175 UINT            status;
176 NX_PACKET       *send_packet[NX_WEB_HTTP_SERVER_NONCE_MAX + 1];
177 NX_PACKET       *send_packet_replay;
178 NX_PACKET       *recv_packet;
179 CHAR            *buffer_ptr;
180 INT             i;
181 
182 
183     /* Give IP task and driver a chance to initialize the system.  */
184     tx_thread_sleep(NX_IP_PERIODIC_RATE);
185 
186     /* Set server IP address.  */
187 #ifdef __PRODUCT_NETXDUO__
188     server_ip_address.nxd_ip_address.v4 = HTTP_SERVER_ADDRESS;
189     server_ip_address.nxd_ip_version = NX_IP_VERSION_V4;
190 #else
191     server_ip_address = HTTP_SERVER_ADDRESS;
192 #endif
193 
194     tx_semaphore_get(&server_start, NX_WAIT_FOREVER);
195 
196     for (i = 0; i < NX_WEB_HTTP_SERVER_NONCE_MAX + 1; i++)
197     {
198 
199         /* Create an HTTP client instance.  */
200         status =  nx_tcp_socket_create(&client_ip, &client_socket[i], "Socket 0",
201                                        NX_IP_NORMAL, NX_FRAGMENT_OKAY, NX_IP_TIME_TO_LIVE, 200,
202                                        NX_NULL, NX_NULL);
203 
204         /* Check status.  */
205         if (status)
206             error_counter++;
207 
208         /* Bind the client socket.  */
209         status = nx_tcp_client_socket_bind(&client_socket[i], NX_ANY_PORT, NX_WAIT_FOREVER);
210 
211         /* Check status of the bind.  */
212         if (status)
213             error_counter++;
214 
215         /* Connect to the HTTP server.  */
216         /* Invoke the 'Duo' (supports IPv6/IPv4) connection call. */
217         status = nxd_tcp_client_socket_connect(&client_socket[i], &server_ip_address,
218                                                NX_WEB_HTTP_SERVER_PORT, NX_WAIT_FOREVER);
219 
220         /* Check status.  */
221         if (status)
222             error_counter++;
223 
224         /* Allocate a packet.  */
225         status = nx_packet_allocate(&client_pool, &send_packet[i], NX_TCP_PACKET, NX_WAIT_FOREVER);
226         if (status)
227             error_counter++;
228 
229         nx_packet_data_append(send_packet[i], pkt, sizeof(pkt), &client_pool, NX_WAIT_FOREVER);
230         nx_packet_data_append(send_packet[i], "\r\n", 2, &client_pool, NX_WAIT_FOREVER);
231 
232         /* Initialize the buffer.  */
233         memset(nonce_buffer, 0, sizeof(nonce_buffer));
234         memset(response_buffer, 0, sizeof(response_buffer));
235 
236         /* Send the request.  */
237         status = nx_tcp_socket_send(&client_socket[i], send_packet[i], NX_WAIT_FOREVER);
238 
239         /* Check status.  */
240         if (status)
241             error_counter++;
242 
243         /* Pickup the response from the Server.  */
244         status = nx_tcp_socket_receive(&client_socket[i], &recv_packet, NX_WAIT_FOREVER);
245 
246         /* Check status.  */
247         if (status)
248             error_counter++;
249         else
250         {
251 
252             if (i == NX_WEB_HTTP_SERVER_NONCE_MAX)
253             {
254                 buffer_ptr = (CHAR *)recv_packet->nx_packet_prepend_ptr;
255 
256                 /* Check the status, no nonce entry, it should be 200. */
257                 if ((buffer_ptr[9] != '5') || (buffer_ptr[10] != '0') || (buffer_ptr[11] != '0'))
258                     error_counter++;
259 
260                 nx_packet_release(recv_packet);
261 
262                 /* Discconect.  */
263                 nx_tcp_socket_disconnect(&client_socket[i], NX_NO_WAIT);
264 
265                 /* Sleep for the allocated nonce to be timed out.  */
266                 tx_thread_sleep(NX_WEB_HTTP_SERVER_NONCE_TIMEOUT + NX_IP_PERIODIC_RATE);
267 
268                 /* Reconnect to HTTP server.  */
269                 status = nxd_tcp_client_socket_connect(&client_socket[i], &server_ip_address,
270                                                        NX_WEB_HTTP_SERVER_PORT, NX_WAIT_FOREVER);
271 
272                 /* Check status.  */
273                 if (status)
274                     error_counter++;
275 
276                 /* Allocate a packet.  */
277                 status = nx_packet_allocate(&client_pool, &send_packet[i], NX_TCP_PACKET, NX_WAIT_FOREVER);
278                 if (status)
279                     error_counter++;
280 
281                 nx_packet_data_append(send_packet[i], pkt, sizeof(pkt), &client_pool, NX_WAIT_FOREVER);
282                 nx_packet_data_append(send_packet[i], "\r\n", 2, &client_pool, NX_WAIT_FOREVER);
283 
284                 /* Initialize the buffer.  */
285                 memset(nonce_buffer, 0, sizeof(nonce_buffer));
286                 memset(response_buffer, 0, sizeof(response_buffer));
287 
288                 /* Send the request.  */
289                 status = nx_tcp_socket_send(&client_socket[i], send_packet[i], NX_WAIT_FOREVER);
290 
291                 /* Check status.  */
292                 if (status)
293                     error_counter++;
294 
295                 /* Pickup the response from the Server.  */
296                 status = nx_tcp_socket_receive(&client_socket[i], &recv_packet, NX_WAIT_FOREVER);
297 
298                 /* Check status.  */
299                 if (status)
300                     error_counter++;
301             }
302 
303             /* Retrieve the nonce.  */
304             status = http_nonce_retrieve(recv_packet, nonce_buffer);
305             if (status)
306                 error_counter++;
307 
308             nx_packet_release(recv_packet);
309         }
310 
311         /* Calculate the response.  */
312         http_digest_response_calculate(&client_md5data, "name", "NetX Duo HTTP demo", "Placeholderpassword", nonce_buffer, "GET",
313                                        "/index.htm", "00000001", "0a4f113b", response_buffer);
314 
315         /* Allocate a packet.  */
316         status = nx_packet_allocate(&client_pool, &send_packet[i], NX_TCP_PACKET, NX_WAIT_FOREVER);
317         if (status)
318             error_counter++;
319 
320         nx_packet_data_append(send_packet[i], pkt, sizeof(pkt), &client_pool, NX_WAIT_FOREVER);
321 
322         /* Build the Authorization header.  */
323         nx_packet_data_append(send_packet[i], "Authorization: Digest", 21, &client_pool, NX_WAIT_FOREVER);
324         nx_packet_data_append(send_packet[i], " username=\"name\",", 17, &client_pool, NX_WAIT_FOREVER);
325         nx_packet_data_append(send_packet[i], " realm=\"NetX Duo HTTP demo\",", 28, &client_pool, NX_WAIT_FOREVER);
326         nx_packet_data_append(send_packet[i], " nonce=\"", 8, &client_pool, NX_WAIT_FOREVER);
327         nx_packet_data_append(send_packet[i], nonce_buffer, NX_WEB_HTTP_SERVER_NONCE_SIZE, &client_pool, NX_WAIT_FOREVER);
328         nx_packet_data_append(send_packet[i], "\",", 2, &client_pool, NX_WAIT_FOREVER);
329         nx_packet_data_append(send_packet[i], " uri=\"/index.htm\",", 17, &client_pool, NX_WAIT_FOREVER);
330         nx_packet_data_append(send_packet[i], " qop=auth,", 10, &client_pool, NX_WAIT_FOREVER);
331         nx_packet_data_append(send_packet[i], " nc=00000001,", 13, &client_pool, NX_WAIT_FOREVER);
332         nx_packet_data_append(send_packet[i], " cnonce=\"0a4f113b\",", 19, &client_pool, NX_WAIT_FOREVER);
333         nx_packet_data_append(send_packet[i], " response=\"", 11, &client_pool, NX_WAIT_FOREVER);
334         nx_packet_data_append(send_packet[i], response_buffer, 32, &client_pool, NX_WAIT_FOREVER);
335         nx_packet_data_append(send_packet[i], "\",", 2, &client_pool, NX_WAIT_FOREVER);
336         nx_packet_data_append(send_packet[i], " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"\r\n", 44, &client_pool, NX_WAIT_FOREVER);
337 
338         nx_packet_data_append(send_packet[i], "\r\n", 2, &client_pool, NX_WAIT_FOREVER);
339 
340         nx_tcp_socket_disconnect(&client_socket[i], NX_NO_WAIT);
341     }
342 
343     /* Copy for replay test.  */
344     nx_packet_copy(send_packet[1], &send_packet_replay, &client_pool, NX_WAIT_FOREVER);
345 
346     for (i = NX_WEB_HTTP_SERVER_NONCE_MAX; i >= 0; i--)
347     {
348 
349         /* Reconnect to the HTTP server.  */
350         status = nxd_tcp_client_socket_connect(&client_socket[i], &server_ip_address,
351                                                NX_WEB_HTTP_SERVER_PORT, NX_WAIT_FOREVER);
352 
353         /* Check status.  */
354         if (status)
355             error_counter++;
356 
357         /* Now send the packet to the HTTP server.  */
358         status = nx_tcp_socket_send(&client_socket[i], send_packet[i], NX_WAIT_FOREVER);
359 
360         /* Check status.  */
361         if (status)
362             error_counter++;
363 
364         /* Pickup the response from the Server.  */
365         status = nx_tcp_socket_receive(&client_socket[i], &recv_packet, NX_WAIT_FOREVER);
366 
367         /* Check status.  */
368         if (status)
369         {
370             error_counter++;
371         }
372         else
373         {
374             buffer_ptr = (CHAR *)recv_packet->nx_packet_prepend_ptr;
375 
376             if (i == 0)
377             {
378                 /* This nonce is timed out, the response should be 401. */
379                 if ((buffer_ptr[9] != '4') || (buffer_ptr[10] != '0') || (buffer_ptr[11] != '1'))
380                     error_counter++;
381             }
382             else
383             {
384                 /* Check the status, If authentication success , it should be 200. */
385                 if ((buffer_ptr[9] != '2') || (buffer_ptr[10] != '0') || (buffer_ptr[11] != '0'))
386                     error_counter++;
387             }
388 
389             nx_packet_release(recv_packet);
390         }
391 
392         if (i != 1)
393         {
394 
395             /* Disconnect and unbind the socket.  */
396             status = nx_tcp_socket_disconnect(&client_socket[i], NX_WEB_HTTP_CLIENT_TIMEOUT);
397             status += nx_tcp_client_socket_unbind(&client_socket[i]);
398             status += nx_tcp_socket_delete(&client_socket[i]);
399             if (status)
400                 error_counter++;
401         }
402     }
403 
404     /* Use the copied packet for replay test.  */
405     /* Create an HTTP client instance.  */
406     status =  nx_tcp_socket_create(&client_ip, &client_socket[0], "Socket 0",
407                                     NX_IP_NORMAL, NX_FRAGMENT_OKAY, NX_IP_TIME_TO_LIVE, 200,
408                                     NX_NULL, NX_NULL);
409 
410     /* Check status.  */
411     if (status)
412         error_counter++;
413 
414     /* Bind the client socket.  */
415     status = nx_tcp_client_socket_bind(&client_socket[0], NX_ANY_PORT, NX_WAIT_FOREVER);
416 
417     /* Check status of the bind.  */
418     if (status)
419         error_counter++;
420 
421     /* Connect to the HTTP server.  */
422     status = nxd_tcp_client_socket_connect(&client_socket[0], &server_ip_address,
423                                             NX_WEB_HTTP_SERVER_PORT, NX_WAIT_FOREVER);
424 
425     /* Check status.  */
426     if (status)
427         error_counter++;
428 
429     /* Now send the packet to the HTTP server.  */
430     status = nx_tcp_socket_send(&client_socket[0], send_packet_replay, NX_WAIT_FOREVER);
431 
432     /* Check status.  */
433     if (status)
434         error_counter++;
435 
436     /* Pickup the response from the Server.  */
437     status = nx_tcp_socket_receive(&client_socket[0], &recv_packet, NX_WAIT_FOREVER);
438 
439     /* Check status.  */
440     if (status)
441     {
442         error_counter++;
443     }
444     else
445     {
446         buffer_ptr = (CHAR *)recv_packet->nx_packet_prepend_ptr;
447 
448         /* This is a replay attack, the response should be 401. */
449         if ((buffer_ptr[9] != '4') || (buffer_ptr[10] != '0') || (buffer_ptr[11] != '1'))
450             error_counter++;
451 
452         nx_packet_release(recv_packet);
453     }
454 
455     for (i = 0; i < 2; i++)
456     {
457 
458         /* Disconnect and unbind the socket.  */
459         status = nx_tcp_socket_disconnect(&client_socket[i], NX_WEB_HTTP_CLIENT_TIMEOUT);
460         status += nx_tcp_client_socket_unbind(&client_socket[i]);
461         status += nx_tcp_socket_delete(&client_socket[i]);
462         if (status)
463             error_counter++;
464     }
465 
466     tx_semaphore_put(&client_stop);
467 
468     if(error_counter)
469     {
470         printf("ERROR!\n");
471         test_control_return(1);
472     }
473     else
474     {
475         printf("SUCCESS!\n");
476         test_control_return(0);
477     }
478 }
479 
480 /* Define the helper HTTP server thread.  */
thread_server_entry(ULONG thread_input)481 void    thread_server_entry(ULONG thread_input)
482 {
483 UINT            status;
484 FX_FILE         my_file;
485 
486 
487     /* Print out test information banner.  */
488     printf("NetX Test:   Web Digest Authenticate Timeout Test......................");
489 
490     /* Check for earlier error.  */
491     if(error_counter)
492     {
493         printf("ERROR!\n");
494         test_control_return(1);
495     }
496 
497     fx_media_format(&ram_disk,
498                     _fx_ram_driver,               // Driver entry
499                     ram_disk_memory,              // RAM disk memory pointer
500                     media_memory,                 // Media buffer pointer
501                     sizeof(media_memory),         // Media buffer size
502                     "MY_RAM_DISK",                // Volume Name
503                     1,                            // Number of FATs
504                     32,                           // Directory Entries
505                     0,                            // Hidden sectors
506                     256,                          // Total sectors
507                     512,                          // Sector size
508                     8,                            // Sectors per cluster
509                     1,                            // Heads
510                     1);                           // Sectors per track
511 
512     /* Open the RAM disk.  */
513     status = fx_media_open(&ram_disk, "RAM DISK", _fx_ram_driver, ram_disk_memory, media_memory, sizeof(media_memory)) ;
514     if(status)
515         error_counter++;
516 
517     /* Give NetX a chance to initialize the system.  */
518     tx_thread_sleep(NX_IP_PERIODIC_RATE);
519 
520     status = fx_file_create(&ram_disk, "index.htm");
521     status += fx_file_open(&ram_disk, &my_file, "index.htm", FX_OPEN_FOR_WRITE);
522     status += fx_file_write(&my_file, "https server", 12);
523     status += fx_file_close(&my_file);
524     if (status)
525         error_counter++;
526 
527     /* Create the HTTP Server.  */
528     status = nx_web_http_server_create(&my_server, "My HTTP Server", &server_ip, NX_WEB_HTTP_SERVER_PORT, &ram_disk,
529                                        &server_stack, sizeof(server_stack), &server_pool,
530                                        authentication_check, NX_NULL);
531     if (status)
532         error_counter++;
533 
534     /* OK to start the HTTP Server.   */
535     status = nx_web_http_server_start(&my_server);
536     if (status)
537         error_counter++;
538 
539     tx_semaphore_put(&server_start);
540 }
541 
542 /* Define the application's authentication check.  This is called by
543    the HTTP server whenever a new request is received.  */
authentication_check(NX_WEB_HTTP_SERVER * server_ptr,UINT request_type,CHAR * resource,CHAR ** name,CHAR ** password,CHAR ** realm)544 static UINT  authentication_check(NX_WEB_HTTP_SERVER *server_ptr, UINT request_type,
545                                   CHAR *resource, CHAR **name, CHAR **password, CHAR **realm)
546 {
547 
548     /* Just use a simple name, password, and realm for all
549        requests and resources.  */
550     *name =     "name";
551     *password = "Placeholderpassword";
552     *realm =    "NetX Duo HTTP demo";
553 
554     /* Request digest authentication.  */
555     return(NX_WEB_HTTP_DIGEST_AUTHENTICATE);
556 }
557 
558 #else
559 
560 #ifdef CTEST
test_application_define(void * first_unused_memory)561 VOID test_application_define(void *first_unused_memory)
562 #else
563 void    netx_web_digest_authenticate_timeout_test_application_define(void *first_unused_memory)
564 #endif
565 {
566 
567     /* Print out test information banner.  */
568     printf("NetX Test:   Web Digest Authenticate Timeout Test......................N/A\n");
569 
570     test_control_return(3);
571 }
572 #endif
573 
574