firmware-utils: honor env SOURCE_DATE_EPOCH
[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 char *soft_ver;
79         const struct flash_partition_entry partitions[MAX_PARTITIONS+1];
80         const char *first_sysupgrade_partition;
81         const char *last_sysupgrade_partition;
82 };
83
84 /** The content of the soft-version structure */
85 struct __attribute__((__packed__)) soft_version {
86         uint32_t magic;
87         uint32_t zero;
88         uint8_t pad1;
89         uint8_t version_major;
90         uint8_t version_minor;
91         uint8_t version_patch;
92         uint8_t year_hi;
93         uint8_t year_lo;
94         uint8_t month;
95         uint8_t day;
96         uint32_t rev;
97         uint8_t pad2;
98 };
99
100
101 static const uint8_t jffs2_eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
102
103
104 /**
105    Salt for the MD5 hash
106
107    Fortunately, TP-LINK seems to use the same salt for most devices which use
108    the new image format.
109 */
110 static const uint8_t md5_salt[16] = {
111         0x7a, 0x2b, 0x15, 0xed,
112         0x9b, 0x98, 0x59, 0x6d,
113         0xe5, 0x04, 0xab, 0x44,
114         0xac, 0x2a, 0x9f, 0x4e,
115 };
116
117
118 /** Firmware layout table */
119 static struct device_info boards[] = {
120         /** Firmware layout for the CPE210/220 */
121         {
122                 .id     = "CPE210",
123                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
124                 .support_list =
125                         "SupportList:\r\n"
126                         "CPE210(TP-LINK|UN|N300-2):1.0\r\n"
127                         "CPE210(TP-LINK|UN|N300-2):1.1\r\n"
128                         "CPE210(TP-LINK|US|N300-2):1.1\r\n"
129                         "CPE210(TP-LINK|EU|N300-2):1.1\r\n"
130                         "CPE220(TP-LINK|UN|N300-2):1.1\r\n"
131                         "CPE220(TP-LINK|US|N300-2):1.1\r\n"
132                         "CPE220(TP-LINK|EU|N300-2):1.1\r\n",
133                 .support_trail = '\xff',
134                 .soft_ver = NULL,
135
136                 .partitions = {
137                         {"fs-uboot", 0x00000, 0x20000},
138                         {"partition-table", 0x20000, 0x02000},
139                         {"default-mac", 0x30000, 0x00020},
140                         {"product-info", 0x31100, 0x00100},
141                         {"signature", 0x32000, 0x00400},
142                         {"os-image", 0x40000, 0x170000},
143                         {"soft-version", 0x1b0000, 0x00100},
144                         {"support-list", 0x1b1000, 0x00400},
145                         {"file-system", 0x1c0000, 0x600000},
146                         {"user-config", 0x7c0000, 0x10000},
147                         {"default-config", 0x7d0000, 0x10000},
148                         {"log", 0x7e0000, 0x10000},
149                         {"radio", 0x7f0000, 0x10000},
150                         {NULL, 0, 0}
151                 },
152
153                 .first_sysupgrade_partition = "os-image",
154                 .last_sysupgrade_partition = "file-system",
155         },
156
157         /** Firmware layout for the CPE510/520 */
158         {
159                 .id     = "CPE510",
160                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
161                 .support_list =
162                         "SupportList:\r\n"
163                         "CPE510(TP-LINK|UN|N300-5):1.0\r\n"
164                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
165                         "CPE510(TP-LINK|UN|N300-5):1.1\r\n"
166                         "CPE510(TP-LINK|US|N300-5):1.1\r\n"
167                         "CPE510(TP-LINK|EU|N300-5):1.1\r\n"
168                         "CPE520(TP-LINK|UN|N300-5):1.1\r\n"
169                         "CPE520(TP-LINK|US|N300-5):1.1\r\n"
170                         "CPE520(TP-LINK|EU|N300-5):1.1\r\n",
171                 .support_trail = '\xff',
172                 .soft_ver = NULL,
173
174                 .partitions = {
175                         {"fs-uboot", 0x00000, 0x20000},
176                         {"partition-table", 0x20000, 0x02000},
177                         {"default-mac", 0x30000, 0x00020},
178                         {"product-info", 0x31100, 0x00100},
179                         {"signature", 0x32000, 0x00400},
180                         {"os-image", 0x40000, 0x170000},
181                         {"soft-version", 0x1b0000, 0x00100},
182                         {"support-list", 0x1b1000, 0x00400},
183                         {"file-system", 0x1c0000, 0x600000},
184                         {"user-config", 0x7c0000, 0x10000},
185                         {"default-config", 0x7d0000, 0x10000},
186                         {"log", 0x7e0000, 0x10000},
187                         {"radio", 0x7f0000, 0x10000},
188                         {NULL, 0, 0}
189                 },
190
191                 .first_sysupgrade_partition = "os-image",
192                 .last_sysupgrade_partition = "file-system",
193         },
194
195         {
196                 .id     = "WBS210",
197                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
198                 .support_list =
199                         "SupportList:\r\n"
200                         "WBS210(TP-LINK|UN|N300-2):1.20\r\n"
201                         "WBS210(TP-LINK|US|N300-2):1.20\r\n"
202                         "WBS210(TP-LINK|EU|N300-2):1.20\r\n",
203                 .support_trail = '\xff',
204                 .soft_ver = NULL,
205
206                 .partitions = {
207                         {"fs-uboot", 0x00000, 0x20000},
208                         {"partition-table", 0x20000, 0x02000},
209                         {"default-mac", 0x30000, 0x00020},
210                         {"product-info", 0x31100, 0x00100},
211                         {"signature", 0x32000, 0x00400},
212                         {"os-image", 0x40000, 0x170000},
213                         {"soft-version", 0x1b0000, 0x00100},
214                         {"support-list", 0x1b1000, 0x00400},
215                         {"file-system", 0x1c0000, 0x600000},
216                         {"user-config", 0x7c0000, 0x10000},
217                         {"default-config", 0x7d0000, 0x10000},
218                         {"log", 0x7e0000, 0x10000},
219                         {"radio", 0x7f0000, 0x10000},
220                         {NULL, 0, 0}
221                 },
222
223                 .first_sysupgrade_partition = "os-image",
224                 .last_sysupgrade_partition = "file-system",
225         },
226
227         {
228                 .id     = "WBS510",
229                 .vendor = "CPE510(TP-LINK|UN|N300-5):1.0\r\n",
230                 .support_list =
231                         "SupportList:\r\n"
232                         "WBS510(TP-LINK|UN|N300-5):1.20\r\n"
233                         "WBS510(TP-LINK|US|N300-5):1.20\r\n"
234                         "WBS510(TP-LINK|EU|N300-5):1.20\r\n",
235                 .support_trail = '\xff',
236                 .soft_ver = NULL,
237
238                 .partitions = {
239                         {"fs-uboot", 0x00000, 0x20000},
240                         {"partition-table", 0x20000, 0x02000},
241                         {"default-mac", 0x30000, 0x00020},
242                         {"product-info", 0x31100, 0x00100},
243                         {"signature", 0x32000, 0x00400},
244                         {"os-image", 0x40000, 0x170000},
245                         {"soft-version", 0x1b0000, 0x00100},
246                         {"support-list", 0x1b1000, 0x00400},
247                         {"file-system", 0x1c0000, 0x600000},
248                         {"user-config", 0x7c0000, 0x10000},
249                         {"default-config", 0x7d0000, 0x10000},
250                         {"log", 0x7e0000, 0x10000},
251                         {"radio", 0x7f0000, 0x10000},
252                         {NULL, 0, 0}
253                 },
254
255                 .first_sysupgrade_partition = "os-image",
256                 .last_sysupgrade_partition = "file-system",
257         },
258
259         /** Firmware layout for the C2600 */
260         {
261                 .id = "C2600",
262                 .vendor = "",
263                 .support_list =
264                         "SupportList:\r\n"
265                         "{product_name:Archer C2600,product_ver:1.0.0,special_id:00000000}\r\n",
266                 .support_trail = '\x00',
267                 .soft_ver = NULL,
268
269                 .partitions = {
270                         {"SBL1", 0x00000, 0x20000},
271                         {"MIBIB", 0x20000, 0x20000},
272                         {"SBL2", 0x40000, 0x20000},
273                         {"SBL3", 0x60000, 0x30000},
274                         {"DDRCONFIG", 0x90000, 0x10000},
275                         {"SSD", 0xa0000, 0x10000},
276                         {"TZ", 0xb0000, 0x30000},
277                         {"RPM", 0xe0000, 0x20000},
278                         {"fs-uboot", 0x100000, 0x70000},
279                         {"uboot-env", 0x170000, 0x40000},
280                         {"radio", 0x1b0000, 0x40000},
281                         {"os-image", 0x1f0000, 0x200000},
282                         {"file-system", 0x3f0000, 0x1b00000},
283                         {"default-mac", 0x1ef0000, 0x00200},
284                         {"pin", 0x1ef0200, 0x00200},
285                         {"product-info", 0x1ef0400, 0x0fc00},
286                         {"partition-table", 0x1f00000, 0x10000},
287                         {"soft-version", 0x1f10000, 0x10000},
288                         {"support-list", 0x1f20000, 0x10000},
289                         {"profile", 0x1f30000, 0x10000},
290                         {"default-config", 0x1f40000, 0x10000},
291                         {"user-config", 0x1f50000, 0x40000},
292                         {"qos-db", 0x1f90000, 0x40000},
293                         {"usb-config", 0x1fd0000, 0x10000},
294                         {"log", 0x1fe0000, 0x20000},
295                         {NULL, 0, 0}
296                 },
297
298                 .first_sysupgrade_partition = "os-image",
299                 .last_sysupgrade_partition = "file-system"
300         },
301
302         /** Firmware layout for the C25v1 */
303         {
304                 .id = "ARCHER-C25-V1",
305                 .support_list =
306                         "SupportList:\n"
307                         "{product_name:ArcherC25,product_ver:1.0.0,special_id:00000000}\n"
308                         "{product_name:ArcherC25,product_ver:1.0.0,special_id:55530000}\n"
309                         "{product_name:ArcherC25,product_ver:1.0.0,special_id:45550000}\n",
310                 .support_trail = '\x00',
311                 .soft_ver = "soft_ver:1.0.0\n",
312
313                 /**
314                     We use a bigger os-image partition than the stock images (and thus
315                     smaller file-system), as our kernel doesn't fit in the stock firmware's
316                     1MB os-image.
317                 */
318                 .partitions = {
319                         {"factory-boot", 0x00000, 0x20000},
320                         {"fs-uboot", 0x20000, 0x10000},
321                         {"os-image", 0x30000, 0x180000},        /* Stock: base 0x30000 size 0x100000 */
322                         {"file-system", 0x1b0000, 0x620000},    /* Stock: base 0x130000 size 0x6a0000 */
323                         {"user-config", 0x7d0000, 0x04000},
324                         {"default-mac", 0x7e0000, 0x00100},
325                         {"device-id", 0x7e0100, 0x00100},
326                         {"extra-para", 0x7e0200, 0x00100},
327                         {"pin", 0x7e0300, 0x00100},
328                         {"support-list", 0x7e0400, 0x00400},
329                         {"soft-version", 0x7e0800, 0x00400},
330                         {"product-info", 0x7e0c00, 0x01400},
331                         {"partition-table", 0x7e2000, 0x01000},
332                         {"profile", 0x7e3000, 0x01000},
333                         {"default-config", 0x7e4000, 0x04000},
334                         {"merge-config", 0x7ec000, 0x02000},
335                         {"qos-db", 0x7ee000, 0x02000},
336                         {"radio", 0x7f0000, 0x10000},
337                         {NULL, 0, 0}
338                 },
339
340                 .first_sysupgrade_partition = "os-image",
341                 .last_sysupgrade_partition = "file-system",
342         },
343
344         /** Firmware layout for the C59v1 */
345         {
346                 .id     = "ARCHER-C59-V1",
347                 .vendor = "",
348                 .support_list =
349                         "SupportList:\r\n"
350                         "{product_name:Archer C59,product_ver:1.0.0,special_id:00000000}\r\n"
351                         "{product_name:Archer C59,product_ver:1.0.0,special_id:45550000}\r\n"
352                         "{product_name:Archer C59,product_ver:1.0.0,special_id:55530000}\r\n",
353                 .support_trail = '\x00',
354                 .soft_ver = "soft_ver:1.0.0\n",
355
356                 .partitions = {
357                         {"fs-uboot", 0x00000, 0x10000},
358                         {"default-mac", 0x10000, 0x00200},
359                         {"pin", 0x10200, 0x00200},
360                         {"device-id", 0x10400, 0x00100},
361                         {"product-info", 0x10500, 0x0fb00},
362                         {"os-image", 0x20000, 0x180000},
363                         {"file-system", 0x1a0000, 0xcb0000},
364                         {"partition-table", 0xe50000, 0x10000},
365                         {"soft-version", 0xe60000, 0x10000},
366                         {"support-list", 0xe70000, 0x10000},
367                         {"profile", 0xe80000, 0x10000},
368                         {"default-config", 0xe90000, 0x10000},
369                         {"user-config", 0xea0000, 0x40000},
370                         {"usb-config", 0xee0000, 0x10000},
371                         {"certificate", 0xef0000, 0x10000},
372                         {"qos-db", 0xf00000, 0x40000},
373                         {"log", 0xfe0000, 0x10000},
374                         {"radio", 0xff0000, 0x10000},
375                         {NULL, 0, 0}
376                 },
377
378                 .first_sysupgrade_partition = "os-image",
379                 .last_sysupgrade_partition = "file-system",
380         },
381
382         /** Firmware layout for the C60v1 */
383         {
384                 .id     = "ARCHER-C60-V1",
385                 .vendor = "",
386                 .support_list =
387                         "SupportList:\r\n"
388                         "{product_name:Archer C60,product_ver:1.0.0,special_id:00000000}\r\n"
389                         "{product_name:Archer C60,product_ver:1.0.0,special_id:45550000}\r\n"
390                         "{product_name:Archer C60,product_ver:1.0.0,special_id:55530000}\r\n",
391                 .support_trail = '\x00',
392                 .soft_ver = "soft_ver:1.0.0\n",
393
394                 .partitions = {
395                         {"fs-uboot", 0x00000, 0x10000},
396                         {"default-mac", 0x10000, 0x00200},
397                         {"pin", 0x10200, 0x00200},
398                         {"product-info", 0x10400, 0x00100},
399                         {"partition-table", 0x10500, 0x00800},
400                         {"soft-version", 0x11300, 0x00200},
401                         {"support-list", 0x11500, 0x00100},
402                         {"device-id", 0x11600, 0x00100},
403                         {"profile", 0x11700, 0x03900},
404                         {"default-config", 0x15000, 0x04000},
405                         {"user-config", 0x19000, 0x04000},
406                         {"os-image", 0x20000, 0x150000},
407                         {"file-system", 0x170000, 0x678000},
408                         {"certyficate", 0x7e8000, 0x08000},
409                         {"radio", 0x7f0000, 0x10000},
410                         {NULL, 0, 0}
411                 },
412
413                 .first_sysupgrade_partition = "os-image",
414                 .last_sysupgrade_partition = "file-system",
415         },
416
417         /** Firmware layout for the C5 */
418         {
419                 .id = "ARCHER-C5-V2",
420                 .vendor = "",
421                 .support_list =
422                         "SupportList:\r\n"
423                         "{product_name:ArcherC5,"
424                         "product_ver:2.0.0,"
425                         "special_id:00000000}\r\n",
426                 .support_trail = '\x00',
427                 .soft_ver = NULL,
428
429                 .partitions = {
430                         {"fs-uboot", 0x00000, 0x40000},
431                         {"os-image", 0x40000, 0x200000},
432                         {"file-system", 0x240000, 0xc00000},
433                         {"default-mac", 0xe40000, 0x00200},
434                         {"pin", 0xe40200, 0x00200},
435                         {"product-info", 0xe40400, 0x00200},
436                         {"partition-table", 0xe50000, 0x10000},
437                         {"soft-version", 0xe60000, 0x00200},
438                         {"support-list", 0xe61000, 0x0f000},
439                         {"profile", 0xe70000, 0x10000},
440                         {"default-config", 0xe80000, 0x10000},
441                         {"user-config", 0xe90000, 0x50000},
442                         {"log", 0xee0000, 0x100000},
443                         {"radio_bk", 0xfe0000, 0x10000},
444                         {"radio", 0xff0000, 0x10000},
445                         {NULL, 0, 0}
446                 },
447
448                 .first_sysupgrade_partition = "os-image",
449                 .last_sysupgrade_partition = "file-system"
450         },
451
452         /** Firmware layout for the C9 */
453         {
454                 .id = "ARCHERC9",
455                 .vendor = "",
456                 .support_list =
457                         "SupportList:\n"
458                         "{product_name:ArcherC9,"
459                         "product_ver:1.0.0,"
460                         "special_id:00000000}\n",
461                 .support_trail = '\x00',
462                 .soft_ver = NULL,
463
464                 .partitions = {
465                         {"fs-uboot", 0x00000, 0x40000},
466                         {"os-image", 0x40000, 0x200000},
467                         {"file-system", 0x240000, 0xc00000},
468                         {"default-mac", 0xe40000, 0x00200},
469                         {"pin", 0xe40200, 0x00200},
470                         {"product-info", 0xe40400, 0x00200},
471                         {"partition-table", 0xe50000, 0x10000},
472                         {"soft-version", 0xe60000, 0x00200},
473                         {"support-list", 0xe61000, 0x0f000},
474                         {"profile", 0xe70000, 0x10000},
475                         {"default-config", 0xe80000, 0x10000},
476                         {"user-config", 0xe90000, 0x50000},
477                         {"log", 0xee0000, 0x100000},
478                         {"radio_bk", 0xfe0000, 0x10000},
479                         {"radio", 0xff0000, 0x10000},
480                         {NULL, 0, 0}
481                 },
482
483                 .first_sysupgrade_partition = "os-image",
484                 .last_sysupgrade_partition = "file-system"
485         },
486
487         /** Firmware layout for the EAP120 */
488         {
489                 .id     = "EAP120",
490                 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
491                 .support_list =
492                         "SupportList:\r\n"
493                         "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
494                 .support_trail = '\xff',
495                 .soft_ver = NULL,
496
497                 .partitions = {
498                         {"fs-uboot", 0x00000, 0x20000},
499                         {"partition-table", 0x20000, 0x02000},
500                         {"default-mac", 0x30000, 0x00020},
501                         {"support-list", 0x31000, 0x00100},
502                         {"product-info", 0x31100, 0x00100},
503                         {"soft-version", 0x32000, 0x00100},
504                         {"os-image", 0x40000, 0x180000},
505                         {"file-system", 0x1c0000, 0x600000},
506                         {"user-config", 0x7c0000, 0x10000},
507                         {"backup-config", 0x7d0000, 0x10000},
508                         {"log", 0x7e0000, 0x10000},
509                         {"radio", 0x7f0000, 0x10000},
510                         {NULL, 0, 0}
511                 },
512
513                 .first_sysupgrade_partition = "os-image",
514                 .last_sysupgrade_partition = "file-system"
515         },
516
517         /** Firmware layout for the TL-WA850RE v2 */
518         {
519                 .id     = "TLWA850REV2",
520                 .vendor = "",
521                 .support_list =
522                         "SupportList:\n"
523                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55530000}\n"
524                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:00000000}\n"
525                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55534100}\n"
526                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:45550000}\n"
527                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4B520000}\n"
528                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:42520000}\n"
529                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4A500000}\n"
530                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:43410000}\n"
531                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:41550000}\n"
532                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:52550000}\n",
533                 .support_trail = '\x00',
534                 .soft_ver = NULL,
535
536                 /**
537                    576KB were moved from file-system to os-image
538                    in comparison to the stock image
539                 */
540                 .partitions = {
541                         {"fs-uboot", 0x00000, 0x20000},
542                         {"os-image", 0x20000, 0x150000},
543                         {"file-system", 0x170000, 0x240000},
544                         {"partition-table", 0x3b0000, 0x02000},
545                         {"default-mac", 0x3c0000, 0x00020},
546                         {"pin", 0x3c0100, 0x00020},
547                         {"product-info", 0x3c1000, 0x01000},
548                         {"soft-version", 0x3c2000, 0x00100},
549                         {"support-list", 0x3c3000, 0x01000},
550                         {"profile", 0x3c4000, 0x08000},
551                         {"user-config", 0x3d0000, 0x10000},
552                         {"default-config", 0x3e0000, 0x10000},
553                         {"radio", 0x3f0000, 0x10000},
554                         {NULL, 0, 0}
555                 },
556
557                 .first_sysupgrade_partition = "os-image",
558                 .last_sysupgrade_partition = "file-system"
559         },
560
561         /** Firmware layout for the TL-WR1043 v4 */
562         {
563                 .id     = "TLWR1043NDV4",
564                 .vendor = "",
565                 .support_list =
566                         "SupportList:\n"
567                         "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
568                 .support_trail = '\x00',
569                 .soft_ver = NULL,
570
571                 /**
572                     We use a bigger os-image partition than the stock images (and thus
573                     smaller file-system), as our kernel doesn't fit in the stock firmware's
574                     1MB os-image.
575                 */
576                 .partitions = {
577                         {"fs-uboot", 0x00000, 0x20000},
578                         {"os-image", 0x20000, 0x180000},
579                         {"file-system", 0x1a0000, 0xdb0000},
580                         {"default-mac", 0xf50000, 0x00200},
581                         {"pin", 0xf50200, 0x00200},
582                         {"product-info", 0xf50400, 0x0fc00},
583                         {"soft-version", 0xf60000, 0x0b000},
584                         {"support-list", 0xf6b000, 0x04000},
585                         {"profile", 0xf70000, 0x04000},
586                         {"default-config", 0xf74000, 0x0b000},
587                         {"user-config", 0xf80000, 0x40000},
588                         {"partition-table", 0xfc0000, 0x10000},
589                         {"log", 0xfd0000, 0x20000},
590                         {"radio", 0xff0000, 0x10000},
591                         {NULL, 0, 0}
592                 },
593
594                 .first_sysupgrade_partition = "os-image",
595                 .last_sysupgrade_partition = "file-system"
596         },
597
598         /** Firmware layout for the TL-WR942N V1 */
599         {
600                 .id     = "TLWR942NV1",
601                 .vendor = "",
602                 .support_list =
603                         "SupportList:\r\n"
604                         "{product_name:TL-WR942N,product_ver:1.0.0,special_id:00000000}\r\n"
605                         "{product_name:TL-WR942N,product_ver:1.0.0,special_id:52550000}\r\n",
606                 .support_trail = '\x00',
607                 .soft_ver = NULL,
608
609                 .partitions = {
610                         {"fs-uboot", 0x00000, 0x20000},
611                         {"os-image", 0x20000, 0x150000},
612                         {"file-system", 0x170000, 0xcd0000},
613                         {"default-mac", 0xe40000, 0x00200},
614                         {"pin", 0xe40200, 0x00200},
615                         {"product-info", 0xe40400, 0x0fc00},
616                         {"partition-table", 0xe50000, 0x10000},
617                         {"soft-version", 0xe60000, 0x10000},
618                         {"support-list", 0xe70000, 0x10000},
619                         {"profile", 0xe80000, 0x10000},
620                         {"default-config", 0xe90000, 0x10000},
621                         {"user-config", 0xea0000, 0x40000},
622                         {"qos-db", 0xee0000, 0x40000},
623                         {"certificate", 0xf20000, 0x10000},
624                         {"usb-config", 0xfb0000, 0x10000},
625                         {"log", 0xfc0000, 0x20000},
626                         {"radio-bk", 0xfe0000, 0x10000},
627                         {"radio", 0xff0000, 0x10000},
628                         {NULL, 0, 0}
629                 },
630
631                 .first_sysupgrade_partition = "os-image",
632                 .last_sysupgrade_partition = "file-system",
633         },
634
635         /** Firmware layout for the RE450 */
636         {
637                 .id = "RE450",
638                 .vendor = "",
639                 .support_list =
640                         "SupportList:\r\n"
641                         "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
642                         "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
643                         "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
644                         "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
645                         "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
646                         "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
647                         "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
648                         "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
649                 .support_trail = '\x00',
650                 .soft_ver = NULL,
651
652                 /**
653                    The flash partition table for RE450;
654                    it is almost the same as the one used by the stock images,
655                    576KB were moved from file-system to os-image.
656                 */
657                 .partitions = {
658                         {"fs-uboot", 0x00000, 0x20000},
659                         {"os-image", 0x20000, 0x150000},
660                         {"file-system", 0x170000, 0x4a0000},
661                         {"partition-table", 0x600000, 0x02000},
662                         {"default-mac", 0x610000, 0x00020},
663                         {"pin", 0x610100, 0x00020},
664                         {"product-info", 0x611100, 0x01000},
665                         {"soft-version", 0x620000, 0x01000},
666                         {"support-list", 0x621000, 0x01000},
667                         {"profile", 0x622000, 0x08000},
668                         {"user-config", 0x630000, 0x10000},
669                         {"default-config", 0x640000, 0x10000},
670                         {"radio", 0x7f0000, 0x10000},
671                         {NULL, 0, 0}
672                 },
673
674                 .first_sysupgrade_partition = "os-image",
675                 .last_sysupgrade_partition = "file-system"
676         },
677
678         {}
679 };
680
681 #define error(_ret, _errno, _str, ...)                          \
682         do {                                                    \
683                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
684                         strerror(_errno));                      \
685                 if (_ret)                                       \
686                         exit(_ret);                             \
687         } while (0)
688
689
690 /** Stores a uint32 as big endian */
691 static inline void put32(uint8_t *buf, uint32_t val) {
692         buf[0] = val >> 24;
693         buf[1] = val >> 16;
694         buf[2] = val >> 8;
695         buf[3] = val;
696 }
697
698 /** Allocates a new image partition */
699 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
700         struct image_partition_entry entry = {name, len, malloc(len)};
701         if (!entry.data)
702                 error(1, errno, "malloc");
703
704         return entry;
705 }
706
707 /** Frees an image partition */
708 static void free_image_partition(struct image_partition_entry entry) {
709         free(entry.data);
710 }
711
712 static time_t source_date_epoch = -1;
713 static void set_source_date_epoch() {
714         char *env = getenv("SOURCE_DATE_EPOCH");
715         char *endptr = env;
716         errno = 0;
717         if (env && *env) {
718                 source_date_epoch = strtoull(env, &endptr, 10);
719                 if (errno || (endptr && *endptr != '\0')) {
720                         fprintf(stderr, "Invalid SOURCE_DATE_EPOCH");
721                         exit(1);
722                 }
723         }
724 }
725
726 /** Generates the partition-table partition */
727 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
728         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
729
730         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
731
732         *(s++) = 0x00;
733         *(s++) = 0x04;
734         *(s++) = 0x00;
735         *(s++) = 0x00;
736
737         size_t i;
738         for (i = 0; p[i].name; i++) {
739                 size_t len = end-s;
740                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
741
742                 if (w > len-1)
743                         error(1, 0, "flash partition table overflow?");
744
745                 s += w;
746         }
747
748         s++;
749
750         memset(s, 0xff, end-s);
751
752         return entry;
753 }
754
755
756 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
757 static inline uint8_t bcd(uint8_t v) {
758         return 0x10 * (v/10) + v%10;
759 }
760
761
762 /** Generates the soft-version partition */
763 static struct image_partition_entry make_soft_version(uint32_t rev) {
764         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
765         struct soft_version *s = (struct soft_version *)entry.data;
766
767         time_t t;
768
769         if (source_date_epoch != -1)
770                 t = source_date_epoch;
771         else if (time(&t) == (time_t)(-1))
772                 error(1, errno, "time");
773
774         struct tm *tm = localtime(&t);
775
776         s->magic = htonl(0x0000000c);
777         s->zero = 0;
778         s->pad1 = 0xff;
779
780         s->version_major = 0;
781         s->version_minor = 0;
782         s->version_patch = 0;
783
784         s->year_hi = bcd((1900+tm->tm_year)/100);
785         s->year_lo = bcd(tm->tm_year%100);
786         s->month = bcd(tm->tm_mon+1);
787         s->day = bcd(tm->tm_mday);
788         s->rev = htonl(rev);
789
790         s->pad2 = 0xff;
791
792         return entry;
793 }
794
795 static struct image_partition_entry make_soft_version_from_string(const char *soft_ver) {
796         /** String length _including_ the terminating zero byte */
797         uint32_t ver_len = strlen(soft_ver) + 1;
798         /** Partition contains 64 bit header, the version string, and one additional null byte */
799         size_t partition_len = 2*sizeof(uint32_t) + ver_len + 1;
800         struct image_partition_entry entry = alloc_image_partition("soft-version", partition_len);
801
802         uint32_t *len = (uint32_t *)entry.data;
803         len[0] = htonl(ver_len);
804         len[1] = 0;
805         memcpy(&len[2], soft_ver, ver_len);
806
807         entry.data[partition_len - 1] = 0;
808
809         return entry;
810 }
811
812 /** Generates the support-list partition */
813 static struct image_partition_entry make_support_list(const struct device_info *info) {
814         size_t len = strlen(info->support_list);
815         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
816
817         put32(entry.data, len);
818         memset(entry.data+4, 0, 4);
819         memcpy(entry.data+8, info->support_list, len);
820         entry.data[len+8] = info->support_trail;
821
822         return entry;
823 }
824
825 /** Creates a new image partition with an arbitrary name from a file */
826 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
827         struct stat statbuf;
828
829         if (stat(filename, &statbuf) < 0)
830                 error(1, errno, "unable to stat file `%s'", filename);
831
832         size_t len = statbuf.st_size;
833
834         if (add_jffs2_eof)
835                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
836
837         struct image_partition_entry entry = alloc_image_partition(part_name, len);
838
839         FILE *file = fopen(filename, "rb");
840         if (!file)
841                 error(1, errno, "unable to open file `%s'", filename);
842
843         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
844                 error(1, errno, "unable to read file `%s'", filename);
845
846         if (add_jffs2_eof) {
847                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
848
849                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
850                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
851         }
852
853         fclose(file);
854
855         return entry;
856 }
857
858 /** Creates a new image partition from arbitrary data */
859 static struct image_partition_entry put_data(const char *part_name, const char *datain, size_t len) {
860
861         struct image_partition_entry entry = alloc_image_partition(part_name, len);
862
863         memcpy(entry.data, datain, len);
864
865         return entry;
866 }
867
868 /**
869    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
870
871    Example image partition table:
872
873      fwup-ptn partition-table base 0x00800 size 0x00800
874      fwup-ptn os-image base 0x01000 size 0x113b45
875      fwup-ptn file-system base 0x114b45 size 0x1d0004
876      fwup-ptn support-list base 0x2e4b49 size 0x000d1
877
878    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
879    the end of the partition table is marked with a zero byte.
880
881    The firmware image must contain at least the partition-table and support-list partitions
882    to be accepted. There aren't any alignment constraints for the image partitions.
883
884    The partition-table partition contains the actual flash layout; partitions
885    from the image partition table are mapped to the corresponding flash partitions during
886    the firmware upgrade. The support-list partition contains a list of devices supported by
887    the firmware image.
888
889    The base offsets in the firmware partition table are relative to the end
890    of the vendor information block, so the partition-table partition will
891    actually start at offset 0x1814 of the image.
892
893    I think partition-table must be the first partition in the firmware image.
894 */
895 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
896         size_t i, j;
897         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
898
899         size_t base = 0x800;
900         for (i = 0; parts[i].name; i++) {
901                 for (j = 0; flash_parts[j].name; j++) {
902                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
903                                 if (parts[i].size > flash_parts[j].size)
904                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
905                                 break;
906                         }
907                 }
908
909                 assert(flash_parts[j].name);
910
911                 memcpy(buffer + base, parts[i].data, parts[i].size);
912
913                 size_t len = end-image_pt;
914                 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);
915
916                 if (w > len-1)
917                         error(1, 0, "image partition table overflow?");
918
919                 image_pt += w;
920
921                 base += parts[i].size;
922         }
923 }
924
925 /** Generates and writes the image MD5 checksum */
926 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
927         MD5_CTX ctx;
928
929         MD5_Init(&ctx);
930         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
931         MD5_Update(&ctx, buffer, len);
932         MD5_Final(md5, &ctx);
933 }
934
935
936 /**
937    Generates the firmware image in factory format
938
939    Image format:
940
941      Bytes (hex)  Usage
942      -----------  -----
943      0000-0003    Image size (4 bytes, big endian)
944      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
945      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
946      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
947                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
948      1014-1813    Image partition table (2048 bytes, padded with 0xff)
949      1814-xxxx    Firmware partitions
950 */
951 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
952         *len = 0x1814;
953
954         size_t i;
955         for (i = 0; parts[i].name; i++)
956                 *len += parts[i].size;
957
958         uint8_t *image = malloc(*len);
959         if (!image)
960                 error(1, errno, "malloc");
961
962         memset(image, 0xff, *len);
963         put32(image, *len);
964
965         if (info->vendor) {
966                 size_t vendor_len = strlen(info->vendor);
967                 put32(image+0x14, vendor_len);
968                 memcpy(image+0x18, info->vendor, vendor_len);
969         }
970
971         put_partitions(image + 0x1014, info->partitions, parts);
972         put_md5(image+0x04, image+0x14, *len-0x14);
973
974         return image;
975 }
976
977 /**
978    Generates the firmware image in sysupgrade format
979
980    This makes some assumptions about the provided flash and image partition tables and
981    should be generalized when TP-LINK starts building its safeloader into hardware with
982    different flash layouts.
983 */
984 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
985         size_t i, j;
986         size_t flash_first_partition_index = 0;
987         size_t flash_last_partition_index = 0;
988         const struct flash_partition_entry *flash_first_partition = NULL;
989         const struct flash_partition_entry *flash_last_partition = NULL;
990         const struct image_partition_entry *image_last_partition = NULL;
991
992         /** Find first and last partitions */
993         for (i = 0; info->partitions[i].name; i++) {
994                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
995                         flash_first_partition = &info->partitions[i];
996                         flash_first_partition_index = i;
997                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
998                         flash_last_partition = &info->partitions[i];
999                         flash_last_partition_index = i;
1000                 }
1001         }
1002
1003         assert(flash_first_partition && flash_last_partition);
1004         assert(flash_first_partition_index < flash_last_partition_index);
1005
1006         /** Find last partition from image to calculate needed size */
1007         for (i = 0; image_parts[i].name; i++) {
1008                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
1009                         image_last_partition = &image_parts[i];
1010                         break;
1011                 }
1012         }
1013
1014         assert(image_last_partition);
1015
1016         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
1017
1018         uint8_t *image = malloc(*len);
1019         if (!image)
1020                 error(1, errno, "malloc");
1021
1022         memset(image, 0xff, *len);
1023
1024         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
1025                 for (j = 0; image_parts[j].name; j++) {
1026                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
1027                                 if (image_parts[j].size > info->partitions[i].size)
1028                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
1029                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
1030                                 break;
1031                         }
1032
1033                         assert(image_parts[j].name);
1034                 }
1035         }
1036
1037         return image;
1038 }
1039
1040 /** Generates an image according to a given layout and writes it to a file */
1041 static void build_image(const char *output,
1042                 const char *kernel_image,
1043                 const char *rootfs_image,
1044                 uint32_t rev,
1045                 bool add_jffs2_eof,
1046                 bool sysupgrade,
1047                 const struct device_info *info) {
1048
1049         struct image_partition_entry parts[7] = {};
1050
1051         parts[0] = make_partition_table(info->partitions);
1052         if (info->soft_ver)
1053                 parts[1] = make_soft_version_from_string(info->soft_ver);
1054         else
1055                 parts[1] = make_soft_version(rev);
1056
1057         parts[2] = make_support_list(info);
1058         parts[3] = read_file("os-image", kernel_image, false);
1059         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
1060
1061         if (strcasecmp(info->id, "ARCHER-C25-V1") == 0) {
1062                 const char mdat[11] = {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
1063                 parts[5] = put_data("extra-para", mdat, 11);
1064         }
1065
1066         size_t len;
1067         void *image;
1068         if (sysupgrade)
1069                 image = generate_sysupgrade_image(info, parts, &len);
1070         else
1071                 image = generate_factory_image(info, parts, &len);
1072
1073         FILE *file = fopen(output, "wb");
1074         if (!file)
1075                 error(1, errno, "unable to open output file");
1076
1077         if (fwrite(image, len, 1, file) != 1)
1078                 error(1, 0, "unable to write output file");
1079
1080         fclose(file);
1081
1082         free(image);
1083
1084         size_t i;
1085         for (i = 0; parts[i].name; i++)
1086                 free_image_partition(parts[i]);
1087 }
1088
1089 /** Usage output */
1090 static void usage(const char *argv0) {
1091         fprintf(stderr,
1092                 "Usage: %s [OPTIONS...]\n"
1093                 "\n"
1094                 "Options:\n"
1095                 "  -B <board>      create image for the board specified with <board>\n"
1096                 "  -k <file>       read kernel image from the file <file>\n"
1097                 "  -r <file>       read rootfs image from the file <file>\n"
1098                 "  -o <file>       write output to the file <file>\n"
1099                 "  -V <rev>        sets the revision number to <rev>\n"
1100                 "  -j              add jffs2 end-of-filesystem markers\n"
1101                 "  -S              create sysupgrade instead of factory image\n"
1102                 "  -h              show this help\n",
1103                 argv0
1104         );
1105 };
1106
1107
1108 static const struct device_info *find_board(const char *id)
1109 {
1110         struct device_info *board = NULL;
1111
1112         for (board = boards; board->id != NULL; board++)
1113                 if (strcasecmp(id, board->id) == 0)
1114                         return board;
1115
1116         return NULL;
1117 }
1118
1119 int main(int argc, char *argv[]) {
1120         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
1121         bool add_jffs2_eof = false, sysupgrade = false;
1122         unsigned rev = 0;
1123         const struct device_info *info;
1124         set_source_date_epoch();
1125
1126         while (true) {
1127                 int c;
1128
1129                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
1130                 if (c == -1)
1131                         break;
1132
1133                 switch (c) {
1134                 case 'B':
1135                         board = optarg;
1136                         break;
1137
1138                 case 'k':
1139                         kernel_image = optarg;
1140                         break;
1141
1142                 case 'r':
1143                         rootfs_image = optarg;
1144                         break;
1145
1146                 case 'o':
1147                         output = optarg;
1148                         break;
1149
1150                 case 'V':
1151                         sscanf(optarg, "r%u", &rev);
1152                         break;
1153
1154                 case 'j':
1155                         add_jffs2_eof = true;
1156                         break;
1157
1158                 case 'S':
1159                         sysupgrade = true;
1160                         break;
1161
1162                 case 'h':
1163                         usage(argv[0]);
1164                         return 0;
1165
1166                 default:
1167                         usage(argv[0]);
1168                         return 1;
1169                 }
1170         }
1171
1172         if (!board)
1173                 error(1, 0, "no board has been specified");
1174         if (!kernel_image)
1175                 error(1, 0, "no kernel image has been specified");
1176         if (!rootfs_image)
1177                 error(1, 0, "no rootfs image has been specified");
1178         if (!output)
1179                 error(1, 0, "no output filename has been specified");
1180
1181         info = find_board(board);
1182
1183         if (info == NULL)
1184                 error(1, 0, "unsupported board %s", board);
1185
1186         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
1187
1188         return 0;
1189 }