1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stdio.h>
6#include <string.h>
7
8#include <device_id.h>
9#include <inet6.h>
10#include <netifc.h>
11#include <xefi.h>
12
13#include <zircon/boot/netboot.h>
14#include <tftp/tftp.h>
15
16#define TFTP_BUF_SZ 2048
17char tftp_session_scratch[TFTP_BUF_SZ];
18char tftp_out_scratch[TFTP_BUF_SZ];
19
20// item being downloaded
21static nbfile* item;
22
23// TFTP file state
24typedef struct {
25    nbfile* netboot_file_data;
26    size_t file_size;
27    unsigned int progress_reported;
28} file_info_t;
29
30// TFTP transport state
31typedef struct {
32    struct ip6_addr_t dest_addr;
33    uint16_t dest_port;
34} transport_info_t;
35
36static uint32_t last_cookie = 0;
37static uint32_t last_cmd = 0;
38static uint32_t last_arg = 0;
39static uint32_t last_ack_cmd = 0;
40static uint32_t last_ack_arg = 0;
41
42static int nb_boot_now = 0;
43static int nb_active = 0;
44
45static char advertise_nodename[64] = "";
46static char advertise_data[256] = "nodename=zircon";
47
48static void send_query_ack(const ip6_addr* addr, uint16_t port,
49                           uint32_t cookie) {
50    uint8_t buffer[256];
51    nbmsg* msg = (void*)buffer;
52    msg->magic = NB_MAGIC;
53    msg->cookie = cookie;
54    msg->cmd = NB_ACK;
55    msg->arg = NB_VERSION_CURRENT;
56    memcpy(msg->data, advertise_nodename, sizeof(advertise_nodename));
57    udp6_send(buffer, sizeof(nbmsg) + strlen(advertise_nodename) + 1,
58              addr, port, NB_SERVER_PORT);
59}
60
61static void advertise(void) {
62    uint8_t buffer[sizeof(nbmsg) + sizeof(advertise_data)];
63    nbmsg* msg = (void*)buffer;
64    msg->magic = NB_MAGIC;
65    msg->cookie = 0;
66    msg->cmd = NB_ADVERTISE;
67    msg->arg = NB_VERSION_CURRENT;
68    size_t data_len = strlen(advertise_data) + 1;
69    memcpy(msg->data, advertise_data, data_len);
70    udp6_send(buffer, sizeof(nbmsg) + data_len, &ip6_ll_all_nodes,
71              NB_ADVERT_PORT, NB_SERVER_PORT);
72}
73
74void netboot_recv(void* data, size_t len, const ip6_addr* saddr, uint16_t sport) {
75    nbmsg* msg = data;
76    nbmsg ack;
77    int do_transmit = 1;
78
79    if (len < sizeof(nbmsg))
80        return;
81    len -= sizeof(nbmsg);
82
83    // printf("netboot: MSG %08x %08x %08x %08x datalen %zu\n",
84    //        msg->magic, msg->cookie, msg->cmd, msg->arg, len);
85
86    if ((last_cookie == msg->cookie) &&
87        (last_cmd == msg->cmd) && (last_arg == msg->arg)) {
88        // host must have missed the ack. resend
89        ack.magic = NB_MAGIC;
90        ack.cookie = last_cookie;
91        ack.cmd = last_ack_cmd;
92        ack.arg = last_ack_arg;
93        goto transmit;
94    }
95
96    ack.cmd = NB_ACK;
97    ack.arg = 0;
98
99    switch (msg->cmd) {
100    case NB_COMMAND:
101        if (len == 0)
102            return;
103        msg->data[len - 1] = 0;
104        break;
105    case NB_SEND_FILE:
106        if (len == 0)
107            return;
108        msg->data[len - 1] = 0;
109        for (int i = 0; i < (len - 1); i++) {
110            if ((msg->data[i] < ' ') || (msg->data[i] > 127)) {
111                msg->data[i] = '.';
112            }
113        }
114        item = netboot_get_buffer((const char*)msg->data, msg->arg);
115        if (item) {
116            item->offset = 0;
117            ack.arg = msg->arg;
118            size_t prefix_len = strlen(NB_FILENAME_PREFIX);
119            const char* filename;
120            if (!strncmp((char*)msg->data, NB_FILENAME_PREFIX, prefix_len)) {
121                filename = &((const char*)msg->data)[prefix_len];
122            } else {
123                filename = (const char*)msg->data;
124            }
125            printf("netboot: Receive File '%s'...\n", filename);
126        } else {
127            printf("netboot: Rejected File '%s'...\n", (char*) msg->data);
128            ack.cmd = NB_ERROR_BAD_FILE;
129        }
130        break;
131
132    case NB_DATA:
133    case NB_LAST_DATA:
134        if (item == 0) {
135            printf("netboot: > received chunk before NB_FILE\n");
136            return;
137        }
138        if (msg->arg != item->offset) {
139            // printf("netboot: < received chunk at offset %d but current offset is %zu\n", msg->arg, item->offset);
140            ack.arg = item->offset;
141            ack.cmd = NB_ACK;
142        } else if ((item->offset + len) > item->size) {
143            ack.cmd = NB_ERROR_TOO_LARGE;
144            ack.arg = msg->arg;
145        } else {
146            memcpy(item->data + item->offset, msg->data, len);
147            item->offset += len;
148            ack.cmd = msg->cmd == NB_LAST_DATA ? NB_FILE_RECEIVED : NB_ACK;
149            if (msg->cmd != NB_LAST_DATA) {
150                do_transmit = 0;
151            }
152        }
153        break;
154    case NB_BOOT:
155        nb_boot_now = 1;
156        printf("netboot: Boot Kernel...\n");
157        break;
158    case NB_QUERY:
159        // Send reply and return w/o getting the netboot state out of sync.
160        send_query_ack(saddr, sport, msg->cookie);
161        return;
162    default:
163        ack.cmd = NB_ERROR_BAD_CMD;
164        ack.arg = 0;
165    }
166
167    last_cookie = msg->cookie;
168    last_cmd = msg->cmd;
169    last_arg = msg->arg;
170    last_ack_cmd = ack.cmd;
171    last_ack_arg = ack.arg;
172
173    ack.cookie = msg->cookie;
174    ack.magic = NB_MAGIC;
175transmit:
176    nb_active = 1;
177    if (do_transmit) {
178        // printf("netboot: MSG %08x %08x %08x %08x\n",
179        //   ack.magic, ack.cookie, ack.cmd, ack.arg);
180
181        udp6_send(&ack, sizeof(ack), saddr, sport, NB_SERVER_PORT);
182    }
183}
184
185static tftp_status buffer_open(const char* filename, size_t size, void* cookie) {
186    file_info_t* file_info = cookie;
187    file_info->netboot_file_data = netboot_get_buffer(filename, size);
188    if (file_info->netboot_file_data == NULL) {
189        printf("netboot: unrecognized file %s - rejecting\n", filename);
190        return TFTP_ERR_INVALID_ARGS;
191    }
192    file_info->netboot_file_data->offset = 0;
193    const char* base_filename;
194    size_t prefix_len = strlen(NB_FILENAME_PREFIX);
195    if (!strncmp(filename, NB_FILENAME_PREFIX, prefix_len)) {
196        base_filename = &filename[prefix_len];
197    } else {
198        base_filename = filename;
199    }
200    printf("Receiving %s [%lu bytes]... ", base_filename, (unsigned long)size);
201    file_info->file_size = size;
202    file_info->progress_reported = 0;
203    return TFTP_NO_ERROR;
204}
205
206static tftp_status buffer_write(const void* data, size_t* len, off_t offset, void* cookie) {
207    file_info_t* file_info = cookie;
208    nbfile* nb_buf_info = file_info->netboot_file_data;
209    if (offset > nb_buf_info->size || (offset + *len) > nb_buf_info->size) {
210        printf("netboot: attempt to write past end of buffer\n");
211        return TFTP_ERR_INVALID_ARGS;
212    }
213    memcpy(&nb_buf_info->data[offset], data, *len);
214    nb_buf_info->offset = offset + *len;
215    if (file_info->file_size >= 100) {
216        unsigned int progress_pct = offset / (file_info->file_size / 100);
217        if ((progress_pct > file_info->progress_reported) &&
218            (progress_pct - file_info->progress_reported >= 5)) {
219            printf("%u%%... ", progress_pct);
220            file_info->progress_reported = progress_pct;
221        }
222    }
223    return TFTP_NO_ERROR;
224}
225
226static void buffer_close(void* cookie) {
227    file_info_t* file_info = cookie;
228    file_info->netboot_file_data = NULL;
229    printf("Done\n");
230}
231
232static tftp_status udp_send(void* data, size_t len, void* cookie) {
233    transport_info_t* transport_info = cookie;
234    int bytes_sent = udp6_send(data, len, &transport_info->dest_addr, transport_info->dest_port,
235                               NB_TFTP_OUTGOING_PORT);
236    return bytes_sent < 0 ? TFTP_ERR_IO : TFTP_NO_ERROR;
237}
238
239static int udp_timeout_set(uint32_t timeout_ms, void* cookie) {
240    // TODO
241    return 0;
242}
243
244static int strcmp8to16(const char* str8, const char16_t* str16) {
245    while (*str8 != '\0' && *str8 == *str16) {
246        str8++;
247        str16++;
248    }
249    return *str8 - *str16;
250}
251
252void tftp_recv(void* data, size_t len, const ip6_addr* daddr, uint16_t dport,
253               const ip6_addr* saddr, uint16_t sport) {
254    static tftp_session* session = NULL;
255    static file_info_t file_info = {.netboot_file_data = NULL};
256    static transport_info_t transport_info = {};
257
258    if (dport == NB_TFTP_INCOMING_PORT) {
259        if (session != NULL) {
260            printf("Aborting to service new connection\n");
261        }
262        // Start TFTP session
263        int ret = tftp_init(&session, tftp_session_scratch, sizeof(tftp_session_scratch));
264        if (ret != TFTP_NO_ERROR) {
265            printf("netboot: failed to initiate tftp session\n");
266            session = NULL;
267            return;
268        }
269
270        // Override our window size on the Acer tablet
271        if (!strcmp8to16("INSYDE Corp.", gSys->FirmwareVendor)) {
272            uint16_t window_size = 8;
273            tftp_set_options(session, NULL, NULL, &window_size);
274        }
275
276        // Initialize file interface
277        tftp_file_interface file_ifc = {NULL, buffer_open, NULL, buffer_write, buffer_close};
278        tftp_session_set_file_interface(session, &file_ifc);
279
280        // Initialize transport interface
281        memcpy(&transport_info.dest_addr, saddr, sizeof(struct ip6_addr_t));
282        transport_info.dest_port = sport;
283        tftp_transport_interface transport_ifc = {udp_send, NULL, udp_timeout_set};
284        tftp_session_set_transport_interface(session, &transport_ifc);
285    } else if (!session) {
286        // Ignore anything sent to the outgoing port unless we've already established a connection
287        return;
288    }
289
290    size_t outlen = sizeof(tftp_out_scratch);
291
292    char err_msg[128];
293    tftp_handler_opts handler_opts = {.inbuf = data,
294                                      .inbuf_sz = len,
295                                      .outbuf = tftp_out_scratch,
296                                      .outbuf_sz = &outlen,
297                                      .err_msg = err_msg,
298                                      .err_msg_sz = sizeof(err_msg)};
299    tftp_status status = tftp_handle_msg(session, &transport_info, &file_info, &handler_opts);
300    if (status < 0) {
301        printf("netboot: tftp protocol error: %s\n", err_msg);
302        session = NULL;
303    } else if (status == TFTP_TRANSFER_COMPLETED) {
304        session = NULL;
305    }
306}
307
308#define FAST_TICK 100
309#define SLOW_TICK 1000
310
311int netboot_init(const char* nodename) {
312    if (netifc_open()) {
313        printf("netboot: Failed to open network interface\n");
314        return -1;
315    }
316    char buf[DEVICE_ID_MAX];
317    if (!nodename || (nodename[0] == 0)) {
318        device_id(eth_addr(), buf);
319        nodename = buf;
320    }
321    if (nodename) {
322        strncpy(advertise_nodename, nodename, sizeof(advertise_nodename) - 1);
323        snprintf(advertise_data, sizeof(advertise_data),
324                 "version=%s;nodename=%s", BOOTLOADER_VERSION, nodename);
325    }
326    return 0;
327}
328
329const char* netboot_nodename() {
330    return advertise_nodename;
331}
332
333static int nb_fastcount = 0;
334static int nb_online = 0;
335
336int netboot_poll(void) {
337    if (netifc_active()) {
338        if (nb_online == 0) {
339            printf("netboot: interface online\n");
340            nb_online = 1;
341            nb_fastcount = 20;
342            netifc_set_timer(FAST_TICK);
343            advertise();
344        }
345    } else {
346        if (nb_online == 1) {
347            printf("netboot: interface offline\n");
348            nb_online = 0;
349        }
350        return 0;
351    }
352    if (netifc_timer_expired()) {
353        if (nb_fastcount) {
354            nb_fastcount--;
355            netifc_set_timer(FAST_TICK);
356        } else {
357            netifc_set_timer(SLOW_TICK);
358        }
359        if (nb_active) {
360            // don't advertise if we're in a transfer
361            nb_active = 0;
362        } else {
363            advertise();
364        }
365    }
366
367    netifc_poll();
368
369    if (nb_boot_now) {
370        nb_boot_now = 0;
371        return 1;
372    } else {
373        return 0;
374    }
375}
376
377void netboot_close(void) {
378    netifc_close();
379}
380