1// SPDX-License-Identifier: GPL-2.0
2/*
3 * WGET/HTTP support driver based on U-BOOT's nfs.c
4 * Copyright Duncan Hare <dh@synoia.com> 2017
5 */
6
7#include <asm/global_data.h>
8#include <command.h>
9#include <common.h>
10#include <display_options.h>
11#include <env.h>
12#include <image.h>
13#include <lmb.h>
14#include <mapmem.h>
15#include <net.h>
16#include <net/tcp.h>
17#include <net/wget.h>
18#include <stdlib.h>
19
20DECLARE_GLOBAL_DATA_PTR;
21
22/* The default, change with environment variable 'httpdstp' */
23#define SERVER_PORT		80
24
25static const char bootfile1[] = "GET ";
26static const char bootfile3[] = " HTTP/1.0\r\n\r\n";
27static const char http_eom[] = "\r\n\r\n";
28static const char http_ok[] = "200";
29static const char content_len[] = "Content-Length";
30static const char linefeed[] = "\r\n";
31static struct in_addr web_server_ip;
32static int our_port;
33static int wget_timeout_count;
34
35struct pkt_qd {
36	uchar *pkt;
37	unsigned int tcp_seq_num;
38	unsigned int len;
39};
40
41/*
42 * This is a control structure for out of order packets received.
43 * The actual packet bufers are in the kernel space, and are
44 * expected to be overwritten by the downloaded image.
45 */
46#define PKTQ_SZ (PKTBUFSRX / 4)
47static struct pkt_qd pkt_q[PKTQ_SZ];
48static int pkt_q_idx;
49static unsigned long content_length;
50static unsigned int packets;
51
52static unsigned int initial_data_seq_num;
53static unsigned int next_data_seq_num;
54
55static enum  wget_state current_wget_state;
56
57static char *image_url;
58static unsigned int wget_timeout = WGET_TIMEOUT;
59
60static enum net_loop_state wget_loop_state;
61
62/* Timeout retry parameters */
63static u8 retry_action;			/* actions for TCP retry */
64static unsigned int retry_tcp_ack_num;	/* TCP retry acknowledge number*/
65static unsigned int retry_tcp_seq_num;	/* TCP retry sequence number */
66static int retry_len;			/* TCP retry length */
67
68static ulong wget_load_size;
69
70/**
71 * wget_init_max_size() - initialize maximum load size
72 *
73 * Return:	0 if success, -1 if fails
74 */
75static int wget_init_load_size(void)
76{
77	struct lmb lmb;
78	phys_size_t max_size;
79
80	lmb_init_and_reserve(&lmb, gd->bd, (void *)gd->fdt_blob);
81
82	max_size = lmb_get_free_size(&lmb, image_load_addr);
83	if (!max_size)
84		return -1;
85
86	wget_load_size = max_size;
87
88	return 0;
89}
90
91/**
92 * store_block() - store block in memory
93 * @src: source of data
94 * @offset: offset
95 * @len: length
96 */
97static inline int store_block(uchar *src, unsigned int offset, unsigned int len)
98{
99	ulong store_addr = image_load_addr + offset;
100	ulong newsize = offset + len;
101	uchar *ptr;
102
103	if (IS_ENABLED(CONFIG_LMB)) {
104		ulong end_addr = image_load_addr + wget_load_size;
105
106		if (!end_addr)
107			end_addr = ULONG_MAX;
108
109		if (store_addr < image_load_addr ||
110		    store_addr + len > end_addr) {
111			printf("\nwget error: ");
112			printf("trying to overwrite reserved memory...\n");
113			return -1;
114		}
115	}
116
117	ptr = map_sysmem(store_addr, len);
118	memcpy(ptr, src, len);
119	unmap_sysmem(ptr);
120
121	if (net_boot_file_size < (offset + len))
122		net_boot_file_size = newsize;
123
124	return 0;
125}
126
127/**
128 * wget_send_stored() - wget response dispatcher
129 *
130 * WARNING, This, and only this, is the place in wget.c where
131 * SEQUENCE NUMBERS are swapped between incoming (RX)
132 * and outgoing (TX).
133 * Procedure wget_handler() is correct for RX traffic.
134 */
135static void wget_send_stored(void)
136{
137	u8 action = retry_action;
138	int len = retry_len;
139	unsigned int tcp_ack_num = retry_tcp_seq_num + (len == 0 ? 1 : len);
140	unsigned int tcp_seq_num = retry_tcp_ack_num;
141	unsigned int server_port;
142	uchar *ptr, *offset;
143
144	server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff;
145
146	switch (current_wget_state) {
147	case WGET_CLOSED:
148		debug_cond(DEBUG_WGET, "wget: send SYN\n");
149		current_wget_state = WGET_CONNECTING;
150		net_send_tcp_packet(0, server_port, our_port, action,
151				    tcp_seq_num, tcp_ack_num);
152		packets = 0;
153		break;
154	case WGET_CONNECTING:
155		pkt_q_idx = 0;
156		net_send_tcp_packet(0, server_port, our_port, action,
157				    tcp_seq_num, tcp_ack_num);
158
159		ptr = net_tx_packet + net_eth_hdr_size() +
160			IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
161		offset = ptr;
162
163		memcpy(offset, &bootfile1, strlen(bootfile1));
164		offset += strlen(bootfile1);
165
166		memcpy(offset, image_url, strlen(image_url));
167		offset += strlen(image_url);
168
169		memcpy(offset, &bootfile3, strlen(bootfile3));
170		offset += strlen(bootfile3);
171		net_send_tcp_packet((offset - ptr), server_port, our_port,
172				    TCP_PUSH, tcp_seq_num, tcp_ack_num);
173		current_wget_state = WGET_CONNECTED;
174		break;
175	case WGET_CONNECTED:
176	case WGET_TRANSFERRING:
177	case WGET_TRANSFERRED:
178		net_send_tcp_packet(0, server_port, our_port, action,
179				    tcp_seq_num, tcp_ack_num);
180		break;
181	}
182}
183
184static void wget_send(u8 action, unsigned int tcp_seq_num,
185		      unsigned int tcp_ack_num, int len)
186{
187	retry_action = action;
188	retry_tcp_ack_num = tcp_ack_num;
189	retry_tcp_seq_num = tcp_seq_num;
190	retry_len = len;
191
192	wget_send_stored();
193}
194
195void wget_fail(char *error_message, unsigned int tcp_seq_num,
196	       unsigned int tcp_ack_num, u8 action)
197{
198	printf("wget: Transfer Fail - %s\n", error_message);
199	net_set_timeout_handler(0, NULL);
200	wget_send(action, tcp_seq_num, tcp_ack_num, 0);
201}
202
203void wget_success(u8 action, unsigned int tcp_seq_num,
204		  unsigned int tcp_ack_num, int len, int packets)
205{
206	printf("Packets received %d, Transfer Successful\n", packets);
207	wget_send(action, tcp_seq_num, tcp_ack_num, len);
208}
209
210/*
211 * Interfaces of U-BOOT
212 */
213static void wget_timeout_handler(void)
214{
215	if (++wget_timeout_count > WGET_RETRY_COUNT) {
216		puts("\nRetry count exceeded; starting again\n");
217		wget_send(TCP_RST, 0, 0, 0);
218		net_start_again();
219	} else {
220		puts("T ");
221		net_set_timeout_handler(wget_timeout +
222					WGET_TIMEOUT * wget_timeout_count,
223					wget_timeout_handler);
224		wget_send_stored();
225	}
226}
227
228#define PKT_QUEUE_OFFSET 0x20000
229#define PKT_QUEUE_PACKET_SIZE 0x800
230
231static void wget_connected(uchar *pkt, unsigned int tcp_seq_num,
232			   u8 action, unsigned int tcp_ack_num, unsigned int len)
233{
234	uchar *pkt_in_q;
235	char *pos;
236	int hlen, i;
237	uchar *ptr1;
238
239	pkt[len] = '\0';
240	pos = strstr((char *)pkt, http_eom);
241
242	if (!pos) {
243		debug_cond(DEBUG_WGET,
244			   "wget: Connected, data before Header %p\n", pkt);
245		pkt_in_q = (void *)image_load_addr + PKT_QUEUE_OFFSET +
246			(pkt_q_idx * PKT_QUEUE_PACKET_SIZE);
247
248		ptr1 = map_sysmem((phys_addr_t)pkt_in_q, len);
249		memcpy(ptr1, pkt, len);
250		unmap_sysmem(ptr1);
251
252		pkt_q[pkt_q_idx].pkt = pkt_in_q;
253		pkt_q[pkt_q_idx].tcp_seq_num = tcp_seq_num;
254		pkt_q[pkt_q_idx].len = len;
255		pkt_q_idx++;
256
257		if (pkt_q_idx >= PKTQ_SZ) {
258			printf("wget: Fatal error, queue overrun!\n");
259			net_set_state(NETLOOP_FAIL);
260
261			return;
262		}
263	} else {
264		debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt);
265		/* sizeof(http_eom) - 1 is the string length of (http_eom) */
266		hlen = pos - (char *)pkt + sizeof(http_eom) - 1;
267		pos = strstr((char *)pkt, linefeed);
268		if (pos > 0)
269			i = pos - (char *)pkt;
270		else
271			i = hlen;
272		printf("%.*s", i,  pkt);
273
274		current_wget_state = WGET_TRANSFERRING;
275
276		initial_data_seq_num = tcp_seq_num + hlen;
277		next_data_seq_num    = tcp_seq_num + len;
278
279		if (strstr((char *)pkt, http_ok) == 0) {
280			debug_cond(DEBUG_WGET,
281				   "wget: Connected Bad Xfer\n");
282			wget_loop_state = NETLOOP_FAIL;
283			wget_send(action, tcp_seq_num, tcp_ack_num, len);
284		} else {
285			debug_cond(DEBUG_WGET,
286				   "wget: Connctd pkt %p  hlen %x\n",
287				   pkt, hlen);
288
289			pos = strstr((char *)pkt, content_len);
290			if (!pos) {
291				content_length = -1;
292			} else {
293				pos += sizeof(content_len) + 2;
294				strict_strtoul(pos, 10, &content_length);
295				debug_cond(DEBUG_WGET,
296					   "wget: Connected Len %lu\n",
297					   content_length);
298			}
299
300			net_boot_file_size = 0;
301
302			if (len > hlen) {
303				if (store_block(pkt + hlen, 0, len - hlen) != 0) {
304					wget_loop_state = NETLOOP_FAIL;
305					wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action);
306					net_set_state(NETLOOP_FAIL);
307					return;
308				}
309			}
310
311			debug_cond(DEBUG_WGET,
312				   "wget: Connected Pkt %p hlen %x\n",
313				   pkt, hlen);
314
315			for (i = 0; i < pkt_q_idx; i++) {
316				int err;
317
318				ptr1 = map_sysmem(
319					(phys_addr_t)(pkt_q[i].pkt),
320					pkt_q[i].len);
321				err = store_block(ptr1,
322					  pkt_q[i].tcp_seq_num -
323					  initial_data_seq_num,
324					  pkt_q[i].len);
325				unmap_sysmem(ptr1);
326				debug_cond(DEBUG_WGET,
327					   "wget: Connctd pkt Q %p len %x\n",
328					   pkt_q[i].pkt, pkt_q[i].len);
329				if (err) {
330					wget_loop_state = NETLOOP_FAIL;
331					wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action);
332					net_set_state(NETLOOP_FAIL);
333					return;
334				}
335			}
336		}
337	}
338	wget_send(action, tcp_seq_num, tcp_ack_num, len);
339}
340
341/**
342 * wget_handler() - TCP handler of wget
343 * @pkt: pointer to the application packet
344 * @dport: destination TCP port
345 * @sip: source IP address
346 * @sport: source TCP port
347 * @tcp_seq_num: TCP sequential number
348 * @tcp_ack_num: TCP acknowledgment number
349 * @action: TCP action (SYN, ACK, FIN, etc)
350 * @len: packet length
351 *
352 * In the "application push" invocation, the TCP header with all
353 * its information is pointed to by the packet pointer.
354 */
355static void wget_handler(uchar *pkt, u16 dport,
356			 struct in_addr sip, u16 sport,
357			 u32 tcp_seq_num, u32 tcp_ack_num,
358			 u8 action, unsigned int len)
359{
360	enum tcp_state wget_tcp_state = tcp_get_tcp_state();
361
362	net_set_timeout_handler(wget_timeout, wget_timeout_handler);
363	packets++;
364
365	switch (current_wget_state) {
366	case WGET_CLOSED:
367		debug_cond(DEBUG_WGET, "wget: Handler: Error!, State wrong\n");
368		break;
369	case WGET_CONNECTING:
370		debug_cond(DEBUG_WGET,
371			   "wget: Connecting In len=%x, Seq=%u, Ack=%u\n",
372			   len, tcp_seq_num, tcp_ack_num);
373		if (!len) {
374			if (wget_tcp_state == TCP_ESTABLISHED) {
375				debug_cond(DEBUG_WGET,
376					   "wget: Cting, send, len=%x\n", len);
377				wget_send(action, tcp_seq_num, tcp_ack_num,
378					  len);
379			} else {
380				printf("%.*s", len,  pkt);
381				wget_fail("wget: Handler Connected Fail\n",
382					  tcp_seq_num, tcp_ack_num, action);
383			}
384		}
385		break;
386	case WGET_CONNECTED:
387		debug_cond(DEBUG_WGET, "wget: Connected seq=%u, len=%x\n",
388			   tcp_seq_num, len);
389		if (!len) {
390			wget_fail("Image not found, no data returned\n",
391				  tcp_seq_num, tcp_ack_num, action);
392		} else {
393			wget_connected(pkt, tcp_seq_num, action, tcp_ack_num, len);
394		}
395		break;
396	case WGET_TRANSFERRING:
397		debug_cond(DEBUG_WGET,
398			   "wget: Transferring, seq=%x, ack=%x,len=%x\n",
399			   tcp_seq_num, tcp_ack_num, len);
400
401		if (next_data_seq_num != tcp_seq_num) {
402			debug_cond(DEBUG_WGET, "wget: seq=%x packet was lost\n", next_data_seq_num);
403			return;
404		}
405		next_data_seq_num = tcp_seq_num + len;
406
407		if (store_block(pkt, tcp_seq_num - initial_data_seq_num, len) != 0) {
408			wget_fail("wget: store error\n",
409				  tcp_seq_num, tcp_ack_num, action);
410			net_set_state(NETLOOP_FAIL);
411			return;
412		}
413
414		switch (wget_tcp_state) {
415		case TCP_FIN_WAIT_2:
416			wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len);
417			fallthrough;
418		case TCP_SYN_SENT:
419		case TCP_SYN_RECEIVED:
420		case TCP_CLOSING:
421		case TCP_FIN_WAIT_1:
422		case TCP_CLOSED:
423			net_set_state(NETLOOP_FAIL);
424			break;
425		case TCP_ESTABLISHED:
426			wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num,
427				  len);
428			wget_loop_state = NETLOOP_SUCCESS;
429			break;
430		case TCP_CLOSE_WAIT:     /* End of transfer */
431			current_wget_state = WGET_TRANSFERRED;
432			wget_send(action | TCP_ACK | TCP_FIN,
433				  tcp_seq_num, tcp_ack_num, len);
434			break;
435		}
436		break;
437	case WGET_TRANSFERRED:
438		printf("Packets received %d, Transfer Successful\n", packets);
439		net_set_state(wget_loop_state);
440		break;
441	}
442}
443
444#define RANDOM_PORT_START 1024
445#define RANDOM_PORT_RANGE 0x4000
446
447/**
448 * random_port() - make port a little random (1024-17407)
449 *
450 * Return: random port number from 1024 to 17407
451 *
452 * This keeps the math somewhat trivial to compute, and seems to work with
453 * all supported protocols/clients/servers
454 */
455static unsigned int random_port(void)
456{
457	return RANDOM_PORT_START + (get_timer(0) % RANDOM_PORT_RANGE);
458}
459
460#define BLOCKSIZE 512
461
462void wget_start(void)
463{
464	image_url = strchr(net_boot_file_name, ':');
465	if (image_url > 0) {
466		web_server_ip = string_to_ip(net_boot_file_name);
467		++image_url;
468		net_server_ip = web_server_ip;
469	} else {
470		web_server_ip = net_server_ip;
471		image_url = net_boot_file_name;
472	}
473
474	debug_cond(DEBUG_WGET,
475		   "wget: Transfer HTTP Server %pI4; our IP %pI4\n",
476		   &web_server_ip, &net_ip);
477
478	/* Check if we need to send across this subnet */
479	if (net_gateway.s_addr && net_netmask.s_addr) {
480		struct in_addr our_net;
481		struct in_addr server_net;
482
483		our_net.s_addr = net_ip.s_addr & net_netmask.s_addr;
484		server_net.s_addr = net_server_ip.s_addr & net_netmask.s_addr;
485		if (our_net.s_addr != server_net.s_addr)
486			debug_cond(DEBUG_WGET,
487				   "wget: sending through gateway %pI4",
488				   &net_gateway);
489	}
490	debug_cond(DEBUG_WGET, "URL '%s'\n", image_url);
491
492	if (net_boot_file_expected_size_in_blocks) {
493		debug_cond(DEBUG_WGET, "wget: Size is 0x%x Bytes = ",
494			   net_boot_file_expected_size_in_blocks * BLOCKSIZE);
495		print_size(net_boot_file_expected_size_in_blocks * BLOCKSIZE,
496			   "");
497	}
498	debug_cond(DEBUG_WGET,
499		   "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr);
500
501	if (IS_ENABLED(CONFIG_LMB)) {
502		if (wget_init_load_size()) {
503			printf("\nwget error: ");
504			printf("trying to overwrite reserved memory...\n");
505			net_set_state(NETLOOP_FAIL);
506			return;
507		}
508	}
509
510	net_set_timeout_handler(wget_timeout, wget_timeout_handler);
511	tcp_set_tcp_handler(wget_handler);
512
513	wget_timeout_count = 0;
514	current_wget_state = WGET_CLOSED;
515
516	our_port = random_port();
517
518	/*
519	 * Zero out server ether to force arp resolution in case
520	 * the server ip for the previous u-boot command, for example dns
521	 * is not the same as the web server ip.
522	 */
523
524	memset(net_server_ethaddr, 0, 6);
525
526	wget_send(TCP_SYN, 0, 0, 0);
527}
528
529#if (IS_ENABLED(CONFIG_CMD_DNS))
530int wget_with_dns(ulong dst_addr, char *uri)
531{
532	int ret;
533	char *s, *host_name, *file_name, *str_copy;
534
535	/*
536	 * Download file using wget.
537	 *
538	 * U-Boot wget takes the target uri in this format.
539	 *  "<http server ip>:<file path>"  e.g.) 192.168.1.1:/sample/test.iso
540	 * Need to resolve the http server ip address before starting wget.
541	 */
542	str_copy = strdup(uri);
543	if (!str_copy)
544		return -ENOMEM;
545
546	s = str_copy + strlen("http://");
547	host_name = strsep(&s, "/");
548	if (!s) {
549		log_err("Error: invalied uri, no file path\n");
550		ret = -EINVAL;
551		goto out;
552	}
553	file_name = s;
554
555	/* TODO: If the given uri has ip address for the http server, skip dns */
556	net_dns_resolve = host_name;
557	net_dns_env_var = "httpserverip";
558	if (net_loop(DNS) < 0) {
559		log_err("Error: dns lookup of %s failed, check setup\n", net_dns_resolve);
560		ret = -EINVAL;
561		goto out;
562	}
563	s = env_get("httpserverip");
564	if (!s) {
565		ret = -EINVAL;
566		goto out;
567	}
568
569	strlcpy(net_boot_file_name, s, sizeof(net_boot_file_name));
570	strlcat(net_boot_file_name, ":/", sizeof(net_boot_file_name)); /* append '/' which is removed by strsep() */
571	strlcat(net_boot_file_name, file_name, sizeof(net_boot_file_name));
572	image_load_addr = dst_addr;
573	ret = net_loop(WGET);
574
575out:
576	free(str_copy);
577
578	return ret;
579}
580#endif
581
582/**
583 * wget_validate_uri() - validate the uri for wget
584 *
585 * @uri:	uri string
586 *
587 * This function follows the current U-Boot wget implementation.
588 * scheme: only "http:" is supported
589 * authority:
590 *   - user information: not supported
591 *   - host: supported
592 *   - port: not supported(always use the default port)
593 *
594 * Uri is expected to be correctly percent encoded.
595 * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0')
596 * and space character(0x20) are not allowed.
597 *
598 * TODO: stricter uri conformance check
599 *
600 * Return:	true on success, false on failure
601 */
602bool wget_validate_uri(char *uri)
603{
604	char c;
605	bool ret = true;
606	char *str_copy, *s, *authority;
607
608	for (c = 0x1; c < 0x21; c++) {
609		if (strchr(uri, c)) {
610			log_err("invalid character is used\n");
611			return false;
612		}
613	}
614	if (strchr(uri, 0x7f)) {
615		log_err("invalid character is used\n");
616		return false;
617	}
618
619	if (strncmp(uri, "http://", 7)) {
620		log_err("only http:// is supported\n");
621		return false;
622	}
623	str_copy = strdup(uri);
624	if (!str_copy)
625		return false;
626
627	s = str_copy + strlen("http://");
628	authority = strsep(&s, "/");
629	if (!s) {
630		log_err("invalid uri, no file path\n");
631		ret = false;
632		goto out;
633	}
634	s = strchr(authority, '@');
635	if (s) {
636		log_err("user information is not supported\n");
637		ret = false;
638		goto out;
639	}
640	s = strchr(authority, ':');
641	if (s) {
642		log_err("user defined port is not supported\n");
643		ret = false;
644		goto out;
645	}
646
647out:
648	free(str_copy);
649
650	return ret;
651}
652