1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * Copyright (C) 2023 The Android Open Source Project
4 */
5
6#include <common.h>
7#include <fastboot.h>
8#include <net.h>
9#include <net/fastboot_tcp.h>
10#include <net/tcp.h>
11
12static char command[FASTBOOT_COMMAND_LEN] = {0};
13static char response[FASTBOOT_RESPONSE_LEN] = {0};
14
15static const unsigned short handshake_length = 4;
16static const uchar *handshake = "FB01";
17
18static u16 curr_sport;
19static u16 curr_dport;
20static u32 curr_tcp_seq_num;
21static u32 curr_tcp_ack_num;
22static unsigned int curr_request_len;
23static enum fastboot_tcp_state {
24	FASTBOOT_CLOSED,
25	FASTBOOT_CONNECTED,
26	FASTBOOT_DISCONNECTING
27} state = FASTBOOT_CLOSED;
28
29static void fastboot_tcp_answer(u8 action, unsigned int len)
30{
31	const u32 response_seq_num = curr_tcp_ack_num;
32	const u32 response_ack_num = curr_tcp_seq_num +
33		  (curr_request_len > 0 ? curr_request_len : 1);
34
35	net_send_tcp_packet(len, htons(curr_sport), htons(curr_dport),
36			    action, response_seq_num, response_ack_num);
37}
38
39static void fastboot_tcp_reset(void)
40{
41	fastboot_tcp_answer(TCP_RST, 0);
42	state = FASTBOOT_CLOSED;
43}
44
45static void fastboot_tcp_send_packet(u8 action, const uchar *data, unsigned int len)
46{
47	uchar *pkt = net_get_async_tx_pkt_buf();
48
49	memset(pkt, '\0', PKTSIZE);
50	pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
51	memcpy(pkt, data, len);
52	fastboot_tcp_answer(action, len);
53	memset(pkt, '\0', PKTSIZE);
54}
55
56static void fastboot_tcp_send_message(const char *message, unsigned int len)
57{
58	__be64 len_be = __cpu_to_be64(len);
59	uchar *pkt = net_get_async_tx_pkt_buf();
60
61	memset(pkt, '\0', PKTSIZE);
62	pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
63	// Put first 8 bytes as a big endian message length
64	memcpy(pkt, &len_be, 8);
65	pkt += 8;
66	memcpy(pkt, message, len);
67	fastboot_tcp_answer(TCP_ACK | TCP_PUSH, len + 8);
68	memset(pkt, '\0', PKTSIZE);
69}
70
71static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport,
72				      struct in_addr sip, u16 sport,
73				      u32 tcp_seq_num, u32 tcp_ack_num,
74				      u8 action, unsigned int len)
75{
76	int fastboot_command_id;
77	u64 command_size;
78	u8 tcp_fin = action & TCP_FIN;
79	u8 tcp_push = action & TCP_PUSH;
80
81	curr_sport = sport;
82	curr_dport = dport;
83	curr_tcp_seq_num = tcp_seq_num;
84	curr_tcp_ack_num = tcp_ack_num;
85	curr_request_len = len;
86
87	switch (state) {
88	case FASTBOOT_CLOSED:
89		if (tcp_push) {
90			if (len != handshake_length ||
91			    strlen(pkt) != handshake_length ||
92			    memcmp(pkt, handshake, handshake_length) != 0) {
93				fastboot_tcp_reset();
94				break;
95			}
96			fastboot_tcp_send_packet(TCP_ACK | TCP_PUSH,
97						 handshake, handshake_length);
98			state = FASTBOOT_CONNECTED;
99		}
100		break;
101	case FASTBOOT_CONNECTED:
102		if (tcp_fin) {
103			fastboot_tcp_answer(TCP_FIN | TCP_ACK, 0);
104			state = FASTBOOT_DISCONNECTING;
105			break;
106		}
107		if (tcp_push) {
108			// First 8 bytes is big endian message length
109			command_size = __be64_to_cpu(*(u64 *)pkt);
110			len -= 8;
111			pkt += 8;
112
113			// Only single packet messages are supported ATM
114			if (strlen(pkt) != command_size) {
115				fastboot_tcp_reset();
116				break;
117			}
118			strlcpy(command, pkt, len + 1);
119			fastboot_command_id = fastboot_handle_command(command, response);
120			fastboot_tcp_send_message(response, strlen(response));
121			fastboot_handle_boot(fastboot_command_id,
122					     strncmp("OKAY", response, 4) == 0);
123		}
124		break;
125	case FASTBOOT_DISCONNECTING:
126		if (tcp_push)
127			state = FASTBOOT_CLOSED;
128		break;
129	}
130
131	memset(command, 0, FASTBOOT_COMMAND_LEN);
132	memset(response, 0, FASTBOOT_RESPONSE_LEN);
133	curr_sport = 0;
134	curr_dport = 0;
135	curr_tcp_seq_num = 0;
136	curr_tcp_ack_num = 0;
137	curr_request_len = 0;
138}
139
140void fastboot_tcp_start_server(void)
141{
142	printf("Using %s device\n", eth_get_name());
143	printf("Listening for fastboot command on tcp %pI4\n", &net_ip);
144
145	tcp_set_tcp_handler(fastboot_tcp_handler_ipv4);
146}
147