Fresh pull from upstream
[librecmc/librecmc.git] / tools / firmware-utils / src / tplink-safeloader.c
1 /*
2   Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
3   All rights reserved.
4
5   Redistribution and use in source and binary forms, with or without
6   modification, are permitted provided that the following conditions are met:
7
8     1. Redistributions of source code must retain the above copyright notice,
9        this list of conditions and the following disclaimer.
10     2. Redistributions in binary form must reproduce the above copyright notice,
11        this list of conditions and the following disclaimer in the documentation
12        and/or other materials provided with the distribution.
13
14   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26
27 /*
28    tplink-safeloader
29
30    Image generation tool for the TP-LINK SafeLoader as seen on
31    TP-LINK Pharos devices (CPE210/220/510/520)
32 */
33
34
35 #include <assert.h>
36 #include <errno.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <arpa/inet.h>
46
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 #include "md5.h"
51
52
53 #define ALIGN(x,a) ({ typeof(a) __a = (a); (((x) + __a - 1) & ~(__a - 1)); })
54
55
56 #define MAX_PARTITIONS  32
57
58 /** An image partition table entry */
59 struct image_partition_entry {
60         const char *name;
61         size_t size;
62         uint8_t *data;
63 };
64
65 /** A flash partition table entry */
66 struct flash_partition_entry {
67         const char *name;
68         uint32_t base;
69         uint32_t size;
70 };
71
72 /** Firmware layout description */
73 struct device_info {
74         const char *id;
75         const char *vendor;
76         const char *support_list;
77         char support_trail;
78         const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
79         const char *first_sysupgrade_partition;
80         const char *last_sysupgrade_partition;
81 };
82
83 /** The content of the soft-version structure */
84 struct __attribute__((__packed__)) soft_version {
85         uint32_t magic;
86         uint32_t zero;
87         uint8_t pad1;
88         uint8_t version_major;
89         uint8_t version_minor;
90         uint8_t version_patch;
91         uint8_t year_hi;
92         uint8_t year_lo;
93         uint8_t month;
94         uint8_t day;
95         uint32_t rev;
96         uint8_t pad2;
97 };
98
99
100 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
101
102
103 /**
104    Salt for the MD5 hash
105
106    Fortunately, TP-LINK seems to use the same salt for most devices which use
107    the new image format.
108 */
109 static const uint8_t md5_salt[16] = {
110         0x7a, 0x2b, 0x15, 0xed,
111         0x9b, 0x98, 0x59, 0x6d,
112         0xe5, 0x04, 0xab, 0x44,
113         0xac, 0x2a, 0x9f, 0x4e,
114 };
115
116
117 /** Firmware layout table */
118 static struct device_info boards[] = {
119         /** Firmware layout for the CPE210/220 */
120         {
121                 .id     = "CPE210",
122                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
123                 .support_list =
124                         "SupportList:\r\n"
125                         "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
126                         "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
127                         "CPE210(TP-LINK|US|N300-2):1.1\r\n"
128                         "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
129                         "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
130                         "CPE220(TP-LINK|US|N300-2):1.1\r\n"
131                         "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
132                 .support_trail = '\xff',
133
134                 .partitions = {
135                         {"fs-uboot", 0x00000, 0x20000},
136                         {"partition-table", 0x20000, 0x02000},
137                         {"default-mac", 0x30000, 0x00020},
138                         {"product-info", 0x31100, 0x00100},
139                         {"signature", 0x32000, 0x00400},
140                         {"os-image", 0x40000, 0x170000},
141                         {"soft-version", 0x1b0000, 0x00100},
142                         {"support-list", 0x1b1000, 0x00400},
143                         {"file-system", 0x1c0000, 0x600000},
144                         {"user-config", 0x7c0000, 0x10000},
145                         {"default-config", 0x7d0000, 0x10000},
146                         {"log", 0x7e0000, 0x10000},
147                         {"radio", 0x7f0000, 0x10000},
148                         {NULL, 0, 0}
149                 },
150
151                 .first_sysupgrade_partition = "os-image",
152                 .last_sysupgrade_partition = "file-system",
153         },
154
155         /** Firmware layout for the CPE510/520 */
156         {
157                 .id     = "CPE510",
158                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
159                 .support_list =
160                         "SupportList:\r\n"
161                         "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
162                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
163                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
164                         "CPE510(TP-LINK|US|N300-5):1.1\r\n"
165                         "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
166                         "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
167                         "CPE520(TP-LINK|US|N300-5):1.1\r\n"
168                         "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
169                 .support_trail = '\xff',
170
171                 .partitions = {
172                         {"fs-uboot", 0x00000, 0x20000},
173                         {"partition-table", 0x20000, 0x02000},
174                         {"default-mac", 0x30000, 0x00020},
175                         {"product-info", 0x31100, 0x00100},
176                         {"signature", 0x32000, 0x00400},
177                         {"os-image", 0x40000, 0x170000},
178                         {"soft-version", 0x1b0000, 0x00100},
179                         {"support-list", 0x1b1000, 0x00400},
180                         {"file-system", 0x1c0000, 0x600000},
181                         {"user-config", 0x7c0000, 0x10000},
182                         {"default-config", 0x7d0000, 0x10000},
183                         {"log", 0x7e0000, 0x10000},
184                         {"radio", 0x7f0000, 0x10000},
185                         {NULL, 0, 0}
186                 },
187
188                 .first_sysupgrade_partition = "os-image",
189                 .last_sysupgrade_partition = "file-system",
190         },
191
192         {
193                 .id     = "WBS210",
194                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
195                 .support_list =
196                         "SupportList:\r\n"
197                         "WBS210(TP-LINK|UN|N300-2):1.20\r\n"
198                         "WBS210(TP-LINK|US|N300-2):1.20\r\n"
199                         "WBS210(TP-LINK|EU|N300-2):1.20\r\n",
200                 .support_trail = '\xff',
201
202                 .partitions = {
203                         {"fs-uboot", 0x00000, 0x20000},
204                         {"partition-table", 0x20000, 0x02000},
205                         {"default-mac", 0x30000, 0x00020},
206                         {"product-info", 0x31100, 0x00100},
207                         {"signature", 0x32000, 0x00400},
208                         {"os-image", 0x40000, 0x170000},
209                         {"soft-version", 0x1b0000, 0x00100},
210                         {"support-list", 0x1b1000, 0x00400},
211                         {"file-system", 0x1c0000, 0x600000},
212                         {"user-config", 0x7c0000, 0x10000},
213                         {"default-config", 0x7d0000, 0x10000},
214                         {"log", 0x7e0000, 0x10000},
215                         {"radio", 0x7f0000, 0x10000},
216                         {NULL, 0, 0}
217                 },
218
219                 .first_sysupgrade_partition = "os-image",
220                 .last_sysupgrade_partition = "file-system",
221         },
222
223         {
224                 .id     = "WBS510",
225                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
226                 .support_list =
227                         "SupportList:\r\n"
228                         "WBS510(TP-LINK|UN|N300-5):1.20\r\n"
229                         "WBS510(TP-LINK|US|N300-5):1.20\r\n"
230                         "WBS510(TP-LINK|EU|N300-5):1.20\r\n",
231                 .support_trail = '\xff',
232
233                 .partitions = {
234                         {"fs-uboot", 0x00000, 0x20000},
235                         {"partition-table", 0x20000, 0x02000},
236                         {"default-mac", 0x30000, 0x00020},
237                         {"product-info", 0x31100, 0x00100},
238                         {"signature", 0x32000, 0x00400},
239                         {"os-image", 0x40000, 0x170000},
240                         {"soft-version", 0x1b0000, 0x00100},
241                         {"support-list", 0x1b1000, 0x00400},
242                         {"file-system", 0x1c0000, 0x600000},
243                         {"user-config", 0x7c0000, 0x10000},
244                         {"default-config", 0x7d0000, 0x10000},
245                         {"log", 0x7e0000, 0x10000},
246                         {"radio", 0x7f0000, 0x10000},
247                         {NULL, 0, 0}
248                 },
249
250                 .first_sysupgrade_partition = "os-image",
251                 .last_sysupgrade_partition = "file-system",
252         },
253
254         /** Firmware layout for the C2600 */
255         {
256                 .id = "C2600",
257                 .vendor = "",
258                 .support_list =
259                         "SupportList:\r\n"
260                         "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
261                 .support_trail = '\x00',
262
263                 .partitions = {
264                         {"SBL1", 0x00000, 0x20000},
265                         {"MIBIB", 0x20000, 0x20000},
266                         {"SBL2", 0x40000, 0x20000},
267                         {"SBL3", 0x60000, 0x30000},
268                         {"DDRCONFIG", 0x90000, 0x10000},
269                         {"SSD", 0xa0000, 0x10000},
270                         {"TZ", 0xb0000, 0x30000},
271                         {"RPM", 0xe0000, 0x20000},
272                         {"fs-uboot", 0x100000, 0x70000},
273                         {"uboot-env", 0x170000, 0x40000},
274                         {"radio", 0x1b0000, 0x40000},
275                         {"os-image", 0x1f0000, 0x200000},
276                         {"file-system", 0x3f0000, 0x1b00000},
277                         {"default-mac", 0x1ef0000, 0x00200},
278                         {"pin", 0x1ef0200, 0x00200},
279                         {"product-info", 0x1ef0400, 0x0fc00},
280                         {"partition-table", 0x1f00000, 0x10000},
281                         {"soft-version", 0x1f10000, 0x10000},
282                         {"support-list", 0x1f20000, 0x10000},
283                         {"profile", 0x1f30000, 0x10000},
284                         {"default-config", 0x1f40000, 0x10000},
285                         {"user-config", 0x1f50000, 0x40000},
286                         {"qos-db", 0x1f90000, 0x40000},
287                         {"usb-config", 0x1fd0000, 0x10000},
288                         {"log", 0x1fe0000, 0x20000},
289                         {NULL, 0, 0}
290                 },
291
292                 .first_sysupgrade_partition = "os-image",
293                 .last_sysupgrade_partition = "file-system"
294         },
295
296         /** Firmware layout for the C9 */
297         {
298                 .id = "ARCHERC9",
299                 .vendor = "",
300                 .support_list =
301                         "SupportList:\n"
302                         "{product_name:ArcherC9,"
303                         "product_ver:1.0.0,"
304                         "special_id:00000000}\n",
305                 .support_trail = '\x00',
306
307                 .partitions = {
308                         {"fs-uboot", 0x00000, 0x40000},
309                         {"os-image", 0x40000, 0x200000},
310                         {"file-system", 0x240000, 0xc00000},
311                         {"default-mac", 0xe40000, 0x00200},
312                         {"pin", 0xe40200, 0x00200},
313                         {"product-info", 0xe40400, 0x00200},
314                         {"partition-table", 0xe50000, 0x10000},
315                         {"soft-version", 0xe60000, 0x00200},
316                         {"support-list", 0xe61000, 0x0f000},
317                         {"profile", 0xe70000, 0x10000},
318                         {"default-config", 0xe80000, 0x10000},
319                         {"user-config", 0xe90000, 0x50000},
320                         {"log", 0xee0000, 0x100000},
321                         {"radio_bk", 0xfe0000, 0x10000},
322                         {"radio", 0xff0000, 0x10000},
323                         {NULL, 0, 0}
324                 },
325
326                 .first_sysupgrade_partition = "os-image",
327                 .last_sysupgrade_partition = "file-system"
328         },
329
330         /** Firmware layout for the EAP120 */
331         {
332                 .id     = "EAP120",
333                 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
334                 .support_list =
335                         "SupportList:\r\n"
336                         "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
337                 .support_trail = '\xff',
338
339                 .partitions = {
340                         {"fs-uboot", 0x00000, 0x20000},
341                         {"partition-table", 0x20000, 0x02000},
342                         {"default-mac", 0x30000, 0x00020},
343                         {"support-list", 0x31000, 0x00100},
344                         {"product-info", 0x31100, 0x00100},
345                         {"soft-version", 0x32000, 0x00100},
346                         {"os-image", 0x40000, 0x180000},
347                         {"file-system", 0x1c0000, 0x600000},
348                         {"user-config", 0x7c0000, 0x10000},
349                         {"backup-config", 0x7d0000, 0x10000},
350                         {"log", 0x7e0000, 0x10000},
351                         {"radio", 0x7f0000, 0x10000},
352                         {NULL, 0, 0}
353                 },
354
355                 .first_sysupgrade_partition = "os-image",
356                 .last_sysupgrade_partition = "file-system"
357         },
358
359         /** Firmware layout for the TL-WR1043 v4 */
360         {
361                 .id     = "TLWR1043NDV4",
362                 .vendor = "",
363                 .support_list =
364                         "SupportList:\n"
365                         "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
366                 .support_trail = '\x00',
367
368                 /**
369                     We use a bigger os-image partition than the stock images (and thus
370                     smaller file-system), as our kernel doesn't fit in the stock firmware's
371                     1MB os-image.
372                 */
373                 .partitions = {
374                         {"fs-uboot", 0x00000, 0x20000},
375                         {"os-image", 0x20000, 0x180000},
376                         {"file-system", 0x1a0000, 0xdb0000},
377                         {"default-mac", 0xf50000, 0x00200},
378                         {"pin", 0xf50200, 0x00200},
379                         {"product-info", 0xf50400, 0x0fc00},
380                         {"soft-version", 0xf60000, 0x0b000},
381                         {"support-list", 0xf6b000, 0x04000},
382                         {"profile", 0xf70000, 0x04000},
383                         {"default-config", 0xf74000, 0x0b000},
384                         {"user-config", 0xf80000, 0x40000},
385                         {"partition-table", 0xfc0000, 0x10000},
386                         {"log", 0xfd0000, 0x20000},
387                         {"radio", 0xff0000, 0x10000},
388                         {NULL, 0, 0}
389                 },
390
391                 .first_sysupgrade_partition = "os-image",
392                 .last_sysupgrade_partition = "file-system"
393         },
394
395         /** Firmware layout for the RE450 */
396         {
397                 .id = "RE450",
398                 .vendor = "",
399                 .support_list =
400                         "SupportList:\r\n"
401                         "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
402                         "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
403                         "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
404                         "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
405                         "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
406                         "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
407                         "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
408                         "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
409                 .support_trail = '\x00',
410
411                 /**
412                    The flash partition table for RE450;
413                    it is almost the same as the one used by the stock images,
414                    576KB were moved from file-system to os-image.
415                 */
416                 .partitions = {
417                         {"fs-uboot", 0x00000, 0x20000},
418                         {"os-image", 0x20000, 0x150000},
419                         {"file-system", 0x170000, 0x4a0000},
420                         {"partition-table", 0x600000, 0x02000},
421                         {"default-mac", 0x610000, 0x00020},
422                         {"pin", 0x610100, 0x00020},
423                         {"product-info", 0x611100, 0x01000},
424                         {"soft-version", 0x620000, 0x01000},
425                         {"support-list", 0x621000, 0x01000},
426                         {"profile", 0x622000, 0x08000},
427                         {"user-config", 0x630000, 0x10000},
428                         {"default-config", 0x640000, 0x10000},
429                         {"radio", 0x7f0000, 0x10000},
430                         {NULL, 0, 0}
431                 },
432
433                 .first_sysupgrade_partition = "os-image",
434                 .last_sysupgrade_partition = "file-system"
435         },
436
437         {}
438 };
439
440 #define error(_ret, _errno, _str, ...)                          \
441         do {                                                    \
442                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
443                         strerror(_errno));                      \
444                 if (_ret)                                       \
445                         exit(_ret);                             \
446         } while (0)
447
448
449 /** Stores a uint32 as big endian */
450 static inline void put32(uint8_t *buf, uint32_t val) {
451         buf[0] = val >> 24;
452         buf[1] = val >> 16;
453         buf[2] = val >> 8;
454         buf[3] = val;
455 }
456
457 /** Allocates a new image partition */
458 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
459         struct image_partition_entry entry = {name, len, malloc(len)};
460         if (!entry.data)
461                 error(1, errno, "malloc");
462
463         return entry;
464 }
465
466 /** Frees an image partition */
467 static void free_image_partition(struct image_partition_entry entry) {
468         free(entry.data);
469 }
470
471 /** Generates the partition-table partition */
472 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
473         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
474
475         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
476
477         *(s++) = 0x00;
478         *(s++) = 0x04;
479         *(s++) = 0x00;
480         *(s++) = 0x00;
481
482         size_t i;
483         for (i = 0; p[i].name; i++) {
484                 size_t len = end-s;
485                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
486
487                 if (w > len-1)
488                         error(1, 0, "flash partition table overflow?");
489
490                 s += w;
491         }
492
493         s++;
494
495         memset(s, 0xff, end-s);
496
497         return entry;
498 }
499
500
501 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
502 static inline uint8_t bcd(uint8_t v) {
503         return 0x10 * (v/10) + v%10;
504 }
505
506
507 /** Generates the soft-version partition */
508 static struct image_partition_entry make_soft_version(uint32_t rev) {
509         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
510         struct soft_version *s = (struct soft_version *)entry.data;
511
512         time_t t;
513
514         if (time(&t) == (time_t)(-1))
515                 error(1, errno, "time");
516
517         struct tm *tm = localtime(&t);
518
519         s->magic = htonl(0x0000000c);
520         s->zero = 0;
521         s->pad1 = 0xff;
522
523         s->version_major = 0;
524         s->version_minor = 0;
525         s->version_patch = 0;
526
527         s->year_hi = bcd((1900+tm->tm_year)/100);
528         s->year_lo = bcd(tm->tm_year%100);
529         s->month = bcd(tm->tm_mon+1);
530         s->day = bcd(tm->tm_mday);
531         s->rev = htonl(rev);
532
533         s->pad2 = 0xff;
534
535         return entry;
536 }
537
538 /** Generates the support-list partition */
539 static struct image_partition_entry make_support_list(const struct device_info *info) {
540         size_t len = strlen(info->support_list);
541         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
542
543         put32(entry.data, len);
544         memset(entry.data+4, 0, 4);
545         memcpy(entry.data+8, info->support_list, len);
546         entry.data[len+8] = info->support_trail;
547
548         return entry;
549 }
550
551 /** Creates a new image partition with an arbitrary name from a file */
552 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
553         struct stat statbuf;
554
555         if (stat(filename, &statbuf) < 0)
556                 error(1, errno, "unable to stat file `%s'", filename);
557
558         size_t len = statbuf.st_size;
559
560         if (add_jffs2_eof)
561                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
562
563         struct image_partition_entry entry = alloc_image_partition(part_name, len);
564
565         FILE *file = fopen(filename, "rb");
566         if (!file)
567                 error(1, errno, "unable to open file `%s'", filename);
568
569         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
570                 error(1, errno, "unable to read file `%s'", filename);
571
572         if (add_jffs2_eof) {
573                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
574
575                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
576                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
577         }
578
579         fclose(file);
580
581         return entry;
582 }
583
584
585 /**
586    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
587
588    Example image partition table:
589
590      fwup-ptn partition-table base 0x00800 size 0x00800
591      fwup-ptn os-image base 0x01000 size 0x113b45
592      fwup-ptn file-system base 0x114b45 size 0x1d0004
593      fwup-ptn support-list base 0x2e4b49 size 0x000d1
594
595    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
596    the end of the partition table is marked with a zero byte.
597
598    The firmware image must contain at least the partition-table and support-list partitions
599    to be accepted. There aren't any alignment constraints for the image partitions.
600
601    The partition-table partition contains the actual flash layout; partitions
602    from the image partition table are mapped to the corresponding flash partitions during
603    the firmware upgrade. The support-list partition contains a list of devices supported by
604    the firmware image.
605
606    The base offsets in the firmware partition table are relative to the end
607    of the vendor information block, so the partition-table partition will
608    actually start at offset 0x1814 of the image.
609
610    I think partition-table must be the first partition in the firmware image.
611 */
612 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
613         size_t i, j;
614         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
615
616         size_t base = 0x800;
617         for (i = 0; parts[i].name; i++) {
618                 for (j = 0; flash_parts[j].name; j++) {
619                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
620                                 if (parts[i].size > flash_parts[j].size)
621                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
622                                 break;
623                         }
624                 }
625
626                 assert(flash_parts[j].name);
627
628                 memcpy(buffer + base, parts[i].data, parts[i].size);
629
630                 size_t len = end-image_pt;
631                 size_t w = snprintf(image_pt, len, "fwup-ptn %s base 0x%05x size 0x%05x\t\r\n", parts[i].name, (unsigned)base, (unsigned)parts[i].size);
632
633                 if (w > len-1)
634                         error(1, 0, "image partition table overflow?");
635
636                 image_pt += w;
637
638                 base += parts[i].size;
639         }
640 }
641
642 /** Generates and writes the image MD5 checksum */
643 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
644         MD5_CTX ctx;
645
646         MD5_Init(&ctx);
647         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
648         MD5_Update(&ctx, buffer, len);
649         MD5_Final(md5, &ctx);
650 }
651
652
653 /**
654    Generates the firmware image in factory format
655
656    Image format:
657
658      Bytes (hex)  Usage
659      -----------  -----
660      0000-0003    Image size (4 bytes, big endian)
661      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
662      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
663      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
664                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
665      1014-1813    Image partition table (2048 bytes, padded with 0xff)
666      1814-xxxx    Firmware partitions
667 */
668 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
669         *len = 0x1814;
670
671         size_t i;
672         for (i = 0; parts[i].name; i++)
673                 *len += parts[i].size;
674
675         uint8_t *image = malloc(*len);
676         if (!image)
677                 error(1, errno, "malloc");
678
679         memset(image, 0xff, *len);
680         put32(image, *len);
681
682         if (info->vendor) {
683                 size_t vendor_len = strlen(info->vendor);
684                 put32(image+0x14, vendor_len);
685                 memcpy(image+0x18, info->vendor, vendor_len);
686         }
687
688         put_partitions(image + 0x1014, info->partitions, parts);
689         put_md5(image+0x04, image+0x14, *len-0x14);
690
691         return image;
692 }
693
694 /**
695    Generates the firmware image in sysupgrade format
696
697    This makes some assumptions about the provided flash and image partition tables and
698    should be generalized when TP-LINK starts building its safeloader into hardware with
699    different flash layouts.
700 */
701 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
702         size_t i, j;
703         size_t flash_first_partition_index = 0;
704         size_t flash_last_partition_index = 0;
705         const struct flash_partition_entry *flash_first_partition = NULL;
706         const struct flash_partition_entry *flash_last_partition = NULL;
707         const struct image_partition_entry *image_last_partition = NULL;
708
709         /** Find first and last partitions */
710         for (i = 0; info->partitions[i].name; i++) {
711                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
712                         flash_first_partition = &info->partitions[i];
713                         flash_first_partition_index = i;
714                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
715                         flash_last_partition = &info->partitions[i];
716                         flash_last_partition_index = i;
717                 }
718         }
719
720         assert(flash_first_partition && flash_last_partition);
721         assert(flash_first_partition_index < flash_last_partition_index);
722
723         /** Find last partition from image to calculate needed size */
724         for (i = 0; image_parts[i].name; i++) {
725                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
726                         image_last_partition = &image_parts[i];
727                         break;
728                 }
729         }
730
731         assert(image_last_partition);
732
733         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
734
735         uint8_t *image = malloc(*len);
736         if (!image)
737                 error(1, errno, "malloc");
738
739         memset(image, 0xff, *len);
740
741         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
742                 for (j = 0; image_parts[j].name; j++) {
743                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
744                                 if (image_parts[j].size > info->partitions[i].size)
745                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
746                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
747                                 break;
748                         }
749
750                         assert(image_parts[j].name);
751                 }
752         }
753
754         return image;
755 }
756
757 /** Generates an image according to a given layout and writes it to a file */
758 static void build_image(const char *output,
759                 const char *kernel_image,
760                 const char *rootfs_image,
761                 uint32_t rev,
762                 bool add_jffs2_eof,
763                 bool sysupgrade,
764                 const struct device_info *info) {
765         struct image_partition_entry parts[6] = {};
766
767         parts[0] = make_partition_table(info->partitions);
768         parts[1] = make_soft_version(rev);
769         parts[2] = make_support_list(info);
770         parts[3] = read_file("os-image", kernel_image, false);
771         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
772
773         size_t len;
774         void *image;
775         if (sysupgrade)
776                 image = generate_sysupgrade_image(info, parts, &len);
777         else
778                 image = generate_factory_image(info, parts, &len);
779
780         FILE *file = fopen(output, "wb");
781         if (!file)
782                 error(1, errno, "unable to open output file");
783
784         if (fwrite(image, len, 1, file) != 1)
785                 error(1, 0, "unable to write output file");
786
787         fclose(file);
788
789         free(image);
790
791         size_t i;
792         for (i = 0; parts[i].name; i++)
793                 free_image_partition(parts[i]);
794 }
795
796 /** Usage output */
797 static void usage(const char *argv0) {
798         fprintf(stderr,
799                 "Usage: %s [OPTIONS...]\n"
800                 "\n"
801                 "Options:\n"
802                 "  -B <board>      create image for the board specified with <board>\n"
803                 "  -k <file>       read kernel image from the file <file>\n"
804                 "  -r <file>       read rootfs image from the file <file>\n"
805                 "  -o <file>       write output to the file <file>\n"
806                 "  -V <rev>        sets the revision number to <rev>\n"
807                 "  -j              add jffs2 end-of-filesystem markers\n"
808                 "  -S              create sysupgrade instead of factory image\n"
809                 "  -h              show this help\n",
810                 argv0
811         );
812 };
813
814
815 static const struct device_info *find_board(const char *id)
816 {
817         struct device_info *board = NULL;
818
819         for (board = boards; board->id != NULL; board++)
820                 if (strcasecmp(id, board->id) == 0)
821                         return board;
822
823         return NULL;
824 }
825
826 int main(int argc, char *argv[]) {
827         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
828         bool add_jffs2_eof = false, sysupgrade = false;
829         unsigned rev = 0;
830         const struct device_info *info;
831
832         while (true) {
833                 int c;
834
835                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
836                 if (c == -1)
837                         break;
838
839                 switch (c) {
840                 case 'B':
841                         board = optarg;
842                         break;
843
844                 case 'k':
845                         kernel_image = optarg;
846                         break;
847
848                 case 'r':
849                         rootfs_image = optarg;
850                         break;
851
852                 case 'o':
853                         output = optarg;
854                         break;
855
856                 case 'V':
857                         sscanf(optarg, "r%u", &rev);
858                         break;
859
860                 case 'j':
861                         add_jffs2_eof = true;
862                         break;
863
864                 case 'S':
865                         sysupgrade = true;
866                         break;
867
868                 case 'h':
869                         usage(argv[0]);
870                         return 0;
871
872                 default:
873                         usage(argv[0]);
874                         return 1;
875                 }
876         }
877
878         if (!board)
879                 error(1, 0, "no board has been specified");
880         if (!kernel_image)
881                 error(1, 0, "no kernel image has been specified");
882         if (!rootfs_image)
883                 error(1, 0, "no rootfs image has been specified");
884         if (!output)
885                 error(1, 0, "no output filename has been specified");
886
887         info = find_board(board);
888
889         if (info == NULL)
890                 error(1, 0, "unsupported board %s", board);
891
892         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
893
894         return 0;
895 }