9c9efc369c23e068687d37122aa8b4879a49d096
[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 C59v1 */
303         {
304                 .id     = "ARCHER-C59-V1",
305                 .vendor = "",
306                 .support_list =
307                         "SupportList:\r\n"
308                         "{product_name:Archer C59,product_ver:1.0.0,special_id:00000000}\r\n"
309                         "{product_name:Archer C59,product_ver:1.0.0,special_id:45550000}\r\n"
310                         "{product_name:Archer C59,product_ver:1.0.0,special_id:55530000}\r\n",
311                 .support_trail = '\x00',
312                 .soft_ver = "soft_ver:1.0.0\n",
313
314                 .partitions = {
315                         {"fs-uboot", 0x00000, 0x10000},
316                         {"default-mac", 0x10000, 0x00200},
317                         {"pin", 0x10200, 0x00200},
318                         {"device-id", 0x10400, 0x00100},
319                         {"product-info", 0x10500, 0x0fb00},
320                         {"os-image", 0x20000, 0x180000},
321                         {"file-system", 0x1a0000, 0xcb0000},
322                         {"partition-table", 0xe50000, 0x10000},
323                         {"soft-version", 0xe60000, 0x10000},
324                         {"support-list", 0xe70000, 0x10000},
325                         {"profile", 0xe80000, 0x10000},
326                         {"default-config", 0xe90000, 0x10000},
327                         {"user-config", 0xea0000, 0x40000},
328                         {"usb-config", 0xee0000, 0x10000},
329                         {"certificate", 0xef0000, 0x10000},
330                         {"qos-db", 0xf00000, 0x40000},
331                         {"log", 0xfe0000, 0x10000},
332                         {"radio", 0xff0000, 0x10000},
333                         {NULL, 0, 0}
334                 },
335
336                 .first_sysupgrade_partition = "os-image",
337                 .last_sysupgrade_partition = "file-system",
338         },
339
340         /** Firmware layout for the C60v1 */
341         {
342                 .id     = "ARCHER-C60-V1",
343                 .vendor = "",
344                 .support_list =
345                         "SupportList:\r\n"
346                         "{product_name:Archer C60,product_ver:1.0.0,special_id:00000000}\r\n"
347                         "{product_name:Archer C60,product_ver:1.0.0,special_id:45550000}\r\n"
348                         "{product_name:Archer C60,product_ver:1.0.0,special_id:55530000}\r\n",
349                 .support_trail = '\x00',
350                 .soft_ver = "soft_ver:1.0.0\n",
351
352                 .partitions = {
353                         {"fs-uboot", 0x00000, 0x10000},
354                         {"default-mac", 0x10000, 0x00200},
355                         {"pin", 0x10200, 0x00200},
356                         {"product-info", 0x10400, 0x00100},
357                         {"partition-table", 0x10500, 0x00800},
358                         {"soft-version", 0x11300, 0x00200},
359                         {"support-list", 0x11500, 0x00100},
360                         {"device-id", 0x11600, 0x00100},
361                         {"profile", 0x11700, 0x03900},
362                         {"default-config", 0x15000, 0x04000},
363                         {"user-config", 0x19000, 0x04000},
364                         {"os-image", 0x20000, 0x150000},
365                         {"file-system", 0x170000, 0x678000},
366                         {"certyficate", 0x7e8000, 0x08000},
367                         {"radio", 0x7f0000, 0x10000},
368                         {NULL, 0, 0}
369                 },
370
371                 .first_sysupgrade_partition = "os-image",
372                 .last_sysupgrade_partition = "file-system",
373         },
374
375         /** Firmware layout for the C5 */
376         {
377                 .id = "ARCHER-C5-V2",
378                 .vendor = "",
379                 .support_list =
380                         "SupportList:\r\n"
381                         "{product_name:ArcherC5,"
382                         "product_ver:2.0.0,"
383                         "special_id:00000000}\r\n",
384                 .support_trail = '\x00',
385                 .soft_ver = NULL,
386
387                 .partitions = {
388                         {"fs-uboot", 0x00000, 0x40000},
389                         {"os-image", 0x40000, 0x200000},
390                         {"file-system", 0x240000, 0xc00000},
391                         {"default-mac", 0xe40000, 0x00200},
392                         {"pin", 0xe40200, 0x00200},
393                         {"product-info", 0xe40400, 0x00200},
394                         {"partition-table", 0xe50000, 0x10000},
395                         {"soft-version", 0xe60000, 0x00200},
396                         {"support-list", 0xe61000, 0x0f000},
397                         {"profile", 0xe70000, 0x10000},
398                         {"default-config", 0xe80000, 0x10000},
399                         {"user-config", 0xe90000, 0x50000},
400                         {"log", 0xee0000, 0x100000},
401                         {"radio_bk", 0xfe0000, 0x10000},
402                         {"radio", 0xff0000, 0x10000},
403                         {NULL, 0, 0}
404                 },
405
406                 .first_sysupgrade_partition = "os-image",
407                 .last_sysupgrade_partition = "file-system"
408         },
409
410         /** Firmware layout for the C9 */
411         {
412                 .id = "ARCHERC9",
413                 .vendor = "",
414                 .support_list =
415                         "SupportList:\n"
416                         "{product_name:ArcherC9,"
417                         "product_ver:1.0.0,"
418                         "special_id:00000000}\n",
419                 .support_trail = '\x00',
420                 .soft_ver = NULL,
421
422                 .partitions = {
423                         {"fs-uboot", 0x00000, 0x40000},
424                         {"os-image", 0x40000, 0x200000},
425                         {"file-system", 0x240000, 0xc00000},
426                         {"default-mac", 0xe40000, 0x00200},
427                         {"pin", 0xe40200, 0x00200},
428                         {"product-info", 0xe40400, 0x00200},
429                         {"partition-table", 0xe50000, 0x10000},
430                         {"soft-version", 0xe60000, 0x00200},
431                         {"support-list", 0xe61000, 0x0f000},
432                         {"profile", 0xe70000, 0x10000},
433                         {"default-config", 0xe80000, 0x10000},
434                         {"user-config", 0xe90000, 0x50000},
435                         {"log", 0xee0000, 0x100000},
436                         {"radio_bk", 0xfe0000, 0x10000},
437                         {"radio", 0xff0000, 0x10000},
438                         {NULL, 0, 0}
439                 },
440
441                 .first_sysupgrade_partition = "os-image",
442                 .last_sysupgrade_partition = "file-system"
443         },
444
445         /** Firmware layout for the EAP120 */
446         {
447                 .id     = "EAP120",
448                 .vendor = "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
449                 .support_list =
450                         "SupportList:\r\n"
451                         "EAP120(TP-LINK|UN|N300-2):1.0\r\n",
452                 .support_trail = '\xff',
453                 .soft_ver = NULL,
454
455                 .partitions = {
456                         {"fs-uboot", 0x00000, 0x20000},
457                         {"partition-table", 0x20000, 0x02000},
458                         {"default-mac", 0x30000, 0x00020},
459                         {"support-list", 0x31000, 0x00100},
460                         {"product-info", 0x31100, 0x00100},
461                         {"soft-version", 0x32000, 0x00100},
462                         {"os-image", 0x40000, 0x180000},
463                         {"file-system", 0x1c0000, 0x600000},
464                         {"user-config", 0x7c0000, 0x10000},
465                         {"backup-config", 0x7d0000, 0x10000},
466                         {"log", 0x7e0000, 0x10000},
467                         {"radio", 0x7f0000, 0x10000},
468                         {NULL, 0, 0}
469                 },
470
471                 .first_sysupgrade_partition = "os-image",
472                 .last_sysupgrade_partition = "file-system"
473         },
474
475         /** Firmware layout for the TL-WA850RE v2 */
476         {
477                 .id     = "TLWA850REV2",
478                 .vendor = "",
479                 .support_list =
480                         "SupportList:\n"
481                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55530000}\n"
482                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:00000000}\n"
483                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:55534100}\n"
484                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:45550000}\n"
485                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4B520000}\n"
486                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:42520000}\n"
487                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:4A500000}\n"
488                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:43410000}\n"
489                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:41550000}\n"
490                         "{product_name:TL-WA850RE,product_ver:2.0.0,special_id:52550000}\n",
491                 .support_trail = '\x00',
492                 .soft_ver = NULL,
493
494                 /**
495                    576KB were moved from file-system to os-image
496                    in comparison to the stock image
497                 */
498                 .partitions = {
499                         {"fs-uboot", 0x00000, 0x20000},
500                         {"os-image", 0x20000, 0x150000},
501                         {"file-system", 0x170000, 0x240000},
502                         {"partition-table", 0x3b0000, 0x02000},
503                         {"default-mac", 0x3c0000, 0x00020},
504                         {"pin", 0x3c0100, 0x00020},
505                         {"product-info", 0x3c1000, 0x01000},
506                         {"soft-version", 0x3c2000, 0x00100},
507                         {"support-list", 0x3c3000, 0x01000},
508                         {"profile", 0x3c4000, 0x08000},
509                         {"user-config", 0x3d0000, 0x10000},
510                         {"default-config", 0x3e0000, 0x10000},
511                         {"radio", 0x3f0000, 0x10000},
512                         {NULL, 0, 0}
513                 },
514
515                 .first_sysupgrade_partition = "os-image",
516                 .last_sysupgrade_partition = "file-system"
517         },
518
519         /** Firmware layout for the TL-WR1043 v4 */
520         {
521                 .id     = "TLWR1043NDV4",
522                 .vendor = "",
523                 .support_list =
524                         "SupportList:\n"
525                         "{product_name:TL-WR1043ND,product_ver:4.0.0,special_id:45550000}\n",
526                 .support_trail = '\x00',
527                 .soft_ver = NULL,
528
529                 /**
530                     We use a bigger os-image partition than the stock images (and thus
531                     smaller file-system), as our kernel doesn't fit in the stock firmware's
532                     1MB os-image.
533                 */
534                 .partitions = {
535                         {"fs-uboot", 0x00000, 0x20000},
536                         {"os-image", 0x20000, 0x180000},
537                         {"file-system", 0x1a0000, 0xdb0000},
538                         {"default-mac", 0xf50000, 0x00200},
539                         {"pin", 0xf50200, 0x00200},
540                         {"product-info", 0xf50400, 0x0fc00},
541                         {"soft-version", 0xf60000, 0x0b000},
542                         {"support-list", 0xf6b000, 0x04000},
543                         {"profile", 0xf70000, 0x04000},
544                         {"default-config", 0xf74000, 0x0b000},
545                         {"user-config", 0xf80000, 0x40000},
546                         {"partition-table", 0xfc0000, 0x10000},
547                         {"log", 0xfd0000, 0x20000},
548                         {"radio", 0xff0000, 0x10000},
549                         {NULL, 0, 0}
550                 },
551
552                 .first_sysupgrade_partition = "os-image",
553                 .last_sysupgrade_partition = "file-system"
554         },
555
556         /** Firmware layout for the RE450 */
557         {
558                 .id = "RE450",
559                 .vendor = "",
560                 .support_list =
561                         "SupportList:\r\n"
562                         "{product_name:RE450,product_ver:1.0.0,special_id:00000000}\r\n"
563                         "{product_name:RE450,product_ver:1.0.0,special_id:55530000}\r\n"
564                         "{product_name:RE450,product_ver:1.0.0,special_id:45550000}\r\n"
565                         "{product_name:RE450,product_ver:1.0.0,special_id:4A500000}\r\n"
566                         "{product_name:RE450,product_ver:1.0.0,special_id:43410000}\r\n"
567                         "{product_name:RE450,product_ver:1.0.0,special_id:41550000}\r\n"
568                         "{product_name:RE450,product_ver:1.0.0,special_id:4B520000}\r\n"
569                         "{product_name:RE450,product_ver:1.0.0,special_id:55534100}\r\n",
570                 .support_trail = '\x00',
571                 .soft_ver = NULL,
572
573                 /**
574                    The flash partition table for RE450;
575                    it is almost the same as the one used by the stock images,
576                    576KB were moved from file-system to os-image.
577                 */
578                 .partitions = {
579                         {"fs-uboot", 0x00000, 0x20000},
580                         {"os-image", 0x20000, 0x150000},
581                         {"file-system", 0x170000, 0x4a0000},
582                         {"partition-table", 0x600000, 0x02000},
583                         {"default-mac", 0x610000, 0x00020},
584                         {"pin", 0x610100, 0x00020},
585                         {"product-info", 0x611100, 0x01000},
586                         {"soft-version", 0x620000, 0x01000},
587                         {"support-list", 0x621000, 0x01000},
588                         {"profile", 0x622000, 0x08000},
589                         {"user-config", 0x630000, 0x10000},
590                         {"default-config", 0x640000, 0x10000},
591                         {"radio", 0x7f0000, 0x10000},
592                         {NULL, 0, 0}
593                 },
594
595                 .first_sysupgrade_partition = "os-image",
596                 .last_sysupgrade_partition = "file-system"
597         },
598
599         {}
600 };
601
602 #define error(_ret, _errno, _str, ...)                          \
603         do {                                                    \
604                 fprintf(stderr, _str ": %s\n", ## __VA_ARGS__,  \
605                         strerror(_errno));                      \
606                 if (_ret)                                       \
607                         exit(_ret);                             \
608         } while (0)
609
610
611 /** Stores a uint32 as big endian */
612 static inline void put32(uint8_t *buf, uint32_t val) {
613         buf[0] = val >> 24;
614         buf[1] = val >> 16;
615         buf[2] = val >> 8;
616         buf[3] = val;
617 }
618
619 /** Allocates a new image partition */
620 static struct image_partition_entry alloc_image_partition(const char *name, size_t len) {
621         struct image_partition_entry entry = {name, len, malloc(len)};
622         if (!entry.data)
623                 error(1, errno, "malloc");
624
625         return entry;
626 }
627
628 /** Frees an image partition */
629 static void free_image_partition(struct image_partition_entry entry) {
630         free(entry.data);
631 }
632
633 /** Generates the partition-table partition */
634 static struct image_partition_entry make_partition_table(const struct flash_partition_entry *p) {
635         struct image_partition_entry entry = alloc_image_partition("partition-table", 0x800);
636
637         char *s = (char *)entry.data, *end = (char *)(s+entry.size);
638
639         *(s++) = 0x00;
640         *(s++) = 0x04;
641         *(s++) = 0x00;
642         *(s++) = 0x00;
643
644         size_t i;
645         for (i = 0; p[i].name; i++) {
646                 size_t len = end-s;
647                 size_t w = snprintf(s, len, "partition %s base 0x%05x size 0x%05x\n", p[i].name, p[i].base, p[i].size);
648
649                 if (w > len-1)
650                         error(1, 0, "flash partition table overflow?");
651
652                 s += w;
653         }
654
655         s++;
656
657         memset(s, 0xff, end-s);
658
659         return entry;
660 }
661
662
663 /** Generates a binary-coded decimal representation of an integer in the range [0, 99] */
664 static inline uint8_t bcd(uint8_t v) {
665         return 0x10 * (v/10) + v%10;
666 }
667
668
669 /** Generates the soft-version partition */
670 static struct image_partition_entry make_soft_version(uint32_t rev) {
671         struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
672         struct soft_version *s = (struct soft_version *)entry.data;
673
674         time_t t;
675
676         if (time(&t) == (time_t)(-1))
677                 error(1, errno, "time");
678
679         struct tm *tm = localtime(&t);
680
681         s->magic = htonl(0x0000000c);
682         s->zero = 0;
683         s->pad1 = 0xff;
684
685         s->version_major = 0;
686         s->version_minor = 0;
687         s->version_patch = 0;
688
689         s->year_hi = bcd((1900+tm->tm_year)/100);
690         s->year_lo = bcd(tm->tm_year%100);
691         s->month = bcd(tm->tm_mon+1);
692         s->day = bcd(tm->tm_mday);
693         s->rev = htonl(rev);
694
695         s->pad2 = 0xff;
696
697         return entry;
698 }
699
700 static struct image_partition_entry make_soft_version_from_string(const char *soft_ver) {
701         /** String length _including_ the terminating zero byte */
702         uint32_t ver_len = strlen(soft_ver) + 1;
703         /** Partition contains 64 bit header, the version string, and one additional null byte */
704         size_t partition_len = 2*sizeof(uint32_t) + ver_len + 1;
705         struct image_partition_entry entry = alloc_image_partition("soft-version", partition_len);
706
707         uint32_t *len = (uint32_t *)entry.data;
708         len[0] = htonl(ver_len);
709         len[1] = 0;
710         memcpy(&len[2], soft_ver, ver_len);
711
712         entry.data[partition_len - 1] = 0;
713
714         return entry;
715 }
716
717 /** Generates the support-list partition */
718 static struct image_partition_entry make_support_list(const struct device_info *info) {
719         size_t len = strlen(info->support_list);
720         struct image_partition_entry entry = alloc_image_partition("support-list", len + 9);
721
722         put32(entry.data, len);
723         memset(entry.data+4, 0, 4);
724         memcpy(entry.data+8, info->support_list, len);
725         entry.data[len+8] = info->support_trail;
726
727         return entry;
728 }
729
730 /** Creates a new image partition with an arbitrary name from a file */
731 static struct image_partition_entry read_file(const char *part_name, const char *filename, bool add_jffs2_eof) {
732         struct stat statbuf;
733
734         if (stat(filename, &statbuf) < 0)
735                 error(1, errno, "unable to stat file `%s'", filename);
736
737         size_t len = statbuf.st_size;
738
739         if (add_jffs2_eof)
740                 len = ALIGN(len, 0x10000) + sizeof(jffs2_eof_mark);
741
742         struct image_partition_entry entry = alloc_image_partition(part_name, len);
743
744         FILE *file = fopen(filename, "rb");
745         if (!file)
746                 error(1, errno, "unable to open file `%s'", filename);
747
748         if (fread(entry.data, statbuf.st_size, 1, file) != 1)
749                 error(1, errno, "unable to read file `%s'", filename);
750
751         if (add_jffs2_eof) {
752                 uint8_t *eof = entry.data + statbuf.st_size, *end = entry.data+entry.size;
753
754                 memset(eof, 0xff, end - eof - sizeof(jffs2_eof_mark));
755                 memcpy(end - sizeof(jffs2_eof_mark), jffs2_eof_mark, sizeof(jffs2_eof_mark));
756         }
757
758         fclose(file);
759
760         return entry;
761 }
762
763
764 /**
765    Copies a list of image partitions into an image buffer and generates the image partition table while doing so
766
767    Example image partition table:
768
769      fwup-ptn partition-table base 0x00800 size 0x00800
770      fwup-ptn os-image base 0x01000 size 0x113b45
771      fwup-ptn file-system base 0x114b45 size 0x1d0004
772      fwup-ptn support-list base 0x2e4b49 size 0x000d1
773
774    Each line of the partition table is terminated with the bytes 09 0d 0a ("\t\r\n"),
775    the end of the partition table is marked with a zero byte.
776
777    The firmware image must contain at least the partition-table and support-list partitions
778    to be accepted. There aren't any alignment constraints for the image partitions.
779
780    The partition-table partition contains the actual flash layout; partitions
781    from the image partition table are mapped to the corresponding flash partitions during
782    the firmware upgrade. The support-list partition contains a list of devices supported by
783    the firmware image.
784
785    The base offsets in the firmware partition table are relative to the end
786    of the vendor information block, so the partition-table partition will
787    actually start at offset 0x1814 of the image.
788
789    I think partition-table must be the first partition in the firmware image.
790 */
791 static void put_partitions(uint8_t *buffer, const struct flash_partition_entry *flash_parts, const struct image_partition_entry *parts) {
792         size_t i, j;
793         char *image_pt = (char *)buffer, *end = image_pt + 0x800;
794
795         size_t base = 0x800;
796         for (i = 0; parts[i].name; i++) {
797                 for (j = 0; flash_parts[j].name; j++) {
798                         if (!strcmp(flash_parts[j].name, parts[i].name)) {
799                                 if (parts[i].size > flash_parts[j].size)
800                                         error(1, 0, "%s partition too big (more than %u bytes)", flash_parts[j].name, (unsigned)flash_parts[j].size);
801                                 break;
802                         }
803                 }
804
805                 assert(flash_parts[j].name);
806
807                 memcpy(buffer + base, parts[i].data, parts[i].size);
808
809                 size_t len = end-image_pt;
810                 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);
811
812                 if (w > len-1)
813                         error(1, 0, "image partition table overflow?");
814
815                 image_pt += w;
816
817                 base += parts[i].size;
818         }
819 }
820
821 /** Generates and writes the image MD5 checksum */
822 static void put_md5(uint8_t *md5, uint8_t *buffer, unsigned int len) {
823         MD5_CTX ctx;
824
825         MD5_Init(&ctx);
826         MD5_Update(&ctx, md5_salt, (unsigned int)sizeof(md5_salt));
827         MD5_Update(&ctx, buffer, len);
828         MD5_Final(md5, &ctx);
829 }
830
831
832 /**
833    Generates the firmware image in factory format
834
835    Image format:
836
837      Bytes (hex)  Usage
838      -----------  -----
839      0000-0003    Image size (4 bytes, big endian)
840      0004-0013    MD5 hash (hash of a 16 byte salt and the image data starting with byte 0x14)
841      0014-0017    Vendor information length (without padding) (4 bytes, big endian)
842      0018-1013    Vendor information (4092 bytes, padded with 0xff; there seem to be older
843                   (VxWorks-based) TP-LINK devices which use a smaller vendor information block)
844      1014-1813    Image partition table (2048 bytes, padded with 0xff)
845      1814-xxxx    Firmware partitions
846 */
847 static void * generate_factory_image(const struct device_info *info, const struct image_partition_entry *parts, size_t *len) {
848         *len = 0x1814;
849
850         size_t i;
851         for (i = 0; parts[i].name; i++)
852                 *len += parts[i].size;
853
854         uint8_t *image = malloc(*len);
855         if (!image)
856                 error(1, errno, "malloc");
857
858         memset(image, 0xff, *len);
859         put32(image, *len);
860
861         if (info->vendor) {
862                 size_t vendor_len = strlen(info->vendor);
863                 put32(image+0x14, vendor_len);
864                 memcpy(image+0x18, info->vendor, vendor_len);
865         }
866
867         put_partitions(image + 0x1014, info->partitions, parts);
868         put_md5(image+0x04, image+0x14, *len-0x14);
869
870         return image;
871 }
872
873 /**
874    Generates the firmware image in sysupgrade format
875
876    This makes some assumptions about the provided flash and image partition tables and
877    should be generalized when TP-LINK starts building its safeloader into hardware with
878    different flash layouts.
879 */
880 static void * generate_sysupgrade_image(const struct device_info *info, const struct image_partition_entry *image_parts, size_t *len) {
881         size_t i, j;
882         size_t flash_first_partition_index = 0;
883         size_t flash_last_partition_index = 0;
884         const struct flash_partition_entry *flash_first_partition = NULL;
885         const struct flash_partition_entry *flash_last_partition = NULL;
886         const struct image_partition_entry *image_last_partition = NULL;
887
888         /** Find first and last partitions */
889         for (i = 0; info->partitions[i].name; i++) {
890                 if (!strcmp(info->partitions[i].name, info->first_sysupgrade_partition)) {
891                         flash_first_partition = &info->partitions[i];
892                         flash_first_partition_index = i;
893                 } else if (!strcmp(info->partitions[i].name, info->last_sysupgrade_partition)) {
894                         flash_last_partition = &info->partitions[i];
895                         flash_last_partition_index = i;
896                 }
897         }
898
899         assert(flash_first_partition && flash_last_partition);
900         assert(flash_first_partition_index < flash_last_partition_index);
901
902         /** Find last partition from image to calculate needed size */
903         for (i = 0; image_parts[i].name; i++) {
904                 if (!strcmp(image_parts[i].name, info->last_sysupgrade_partition)) {
905                         image_last_partition = &image_parts[i];
906                         break;
907                 }
908         }
909
910         assert(image_last_partition);
911
912         *len = flash_last_partition->base - flash_first_partition->base + image_last_partition->size;
913
914         uint8_t *image = malloc(*len);
915         if (!image)
916                 error(1, errno, "malloc");
917
918         memset(image, 0xff, *len);
919
920         for (i = flash_first_partition_index; i <= flash_last_partition_index; i++) {
921                 for (j = 0; image_parts[j].name; j++) {
922                         if (!strcmp(info->partitions[i].name, image_parts[j].name)) {
923                                 if (image_parts[j].size > info->partitions[i].size)
924                                         error(1, 0, "%s partition too big (more than %u bytes)", info->partitions[i].name, (unsigned)info->partitions[i].size);
925                                 memcpy(image + info->partitions[i].base - flash_first_partition->base, image_parts[j].data, image_parts[j].size);
926                                 break;
927                         }
928
929                         assert(image_parts[j].name);
930                 }
931         }
932
933         return image;
934 }
935
936 /** Generates an image according to a given layout and writes it to a file */
937 static void build_image(const char *output,
938                 const char *kernel_image,
939                 const char *rootfs_image,
940                 uint32_t rev,
941                 bool add_jffs2_eof,
942                 bool sysupgrade,
943                 const struct device_info *info) {
944         struct image_partition_entry parts[6] = {};
945
946         parts[0] = make_partition_table(info->partitions);
947         if (info->soft_ver)
948                 parts[1] = make_soft_version_from_string(info->soft_ver);
949         else
950                 parts[1] = make_soft_version(rev);
951
952         parts[2] = make_support_list(info);
953         parts[3] = read_file("os-image", kernel_image, false);
954         parts[4] = read_file("file-system", rootfs_image, add_jffs2_eof);
955
956         size_t len;
957         void *image;
958         if (sysupgrade)
959                 image = generate_sysupgrade_image(info, parts, &len);
960         else
961                 image = generate_factory_image(info, parts, &len);
962
963         FILE *file = fopen(output, "wb");
964         if (!file)
965                 error(1, errno, "unable to open output file");
966
967         if (fwrite(image, len, 1, file) != 1)
968                 error(1, 0, "unable to write output file");
969
970         fclose(file);
971
972         free(image);
973
974         size_t i;
975         for (i = 0; parts[i].name; i++)
976                 free_image_partition(parts[i]);
977 }
978
979 /** Usage output */
980 static void usage(const char *argv0) {
981         fprintf(stderr,
982                 "Usage: %s [OPTIONS...]\n"
983                 "\n"
984                 "Options:\n"
985                 "  -B <board>      create image for the board specified with <board>\n"
986                 "  -k <file>       read kernel image from the file <file>\n"
987                 "  -r <file>       read rootfs image from the file <file>\n"
988                 "  -o <file>       write output to the file <file>\n"
989                 "  -V <rev>        sets the revision number to <rev>\n"
990                 "  -j              add jffs2 end-of-filesystem markers\n"
991                 "  -S              create sysupgrade instead of factory image\n"
992                 "  -h              show this help\n",
993                 argv0
994         );
995 };
996
997
998 static const struct device_info *find_board(const char *id)
999 {
1000         struct device_info *board = NULL;
1001
1002         for (board = boards; board->id != NULL; board++)
1003                 if (strcasecmp(id, board->id) == 0)
1004                         return board;
1005
1006         return NULL;
1007 }
1008
1009 int main(int argc, char *argv[]) {
1010         const char *board = NULL, *kernel_image = NULL, *rootfs_image = NULL, *output = NULL;
1011         bool add_jffs2_eof = false, sysupgrade = false;
1012         unsigned rev = 0;
1013         const struct device_info *info;
1014
1015         while (true) {
1016                 int c;
1017
1018                 c = getopt(argc, argv, "B:k:r:o:V:jSh");
1019                 if (c == -1)
1020                         break;
1021
1022                 switch (c) {
1023                 case 'B':
1024                         board = optarg;
1025                         break;
1026
1027                 case 'k':
1028                         kernel_image = optarg;
1029                         break;
1030
1031                 case 'r':
1032                         rootfs_image = optarg;
1033                         break;
1034
1035                 case 'o':
1036                         output = optarg;
1037                         break;
1038
1039                 case 'V':
1040                         sscanf(optarg, "r%u", &rev);
1041                         break;
1042
1043                 case 'j':
1044                         add_jffs2_eof = true;
1045                         break;
1046
1047                 case 'S':
1048                         sysupgrade = true;
1049                         break;
1050
1051                 case 'h':
1052                         usage(argv[0]);
1053                         return 0;
1054
1055                 default:
1056                         usage(argv[0]);
1057                         return 1;
1058                 }
1059         }
1060
1061         if (!board)
1062                 error(1, 0, "no board has been specified");
1063         if (!kernel_image)
1064                 error(1, 0, "no kernel image has been specified");
1065         if (!rootfs_image)
1066                 error(1, 0, "no rootfs image has been specified");
1067         if (!output)
1068                 error(1, 0, "no output filename has been specified");
1069
1070         info = find_board(board);
1071
1072         if (info == NULL)
1073                 error(1, 0, "unsupported board %s", board);
1074
1075         build_image(output, kernel_image, rootfs_image, rev, add_jffs2_eof, sysupgrade, info);
1076
1077         return 0;
1078 }