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