├── .gitignore ├── LICENSE ├── LICENSE.smblib ├── Makefile ├── Makefile.m68k ├── README.md ├── cpptest.cpp ├── crc16.c ├── crc32.c ├── include ├── acutest.h ├── crc16.h ├── crc32.h ├── embedded.h ├── zheaders.h ├── zmodem.h ├── znumbers.h ├── zserial.h └── ztypes.h ├── rz.c ├── tests.c ├── zheaders.c ├── znumbers.c └── zserial.c /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .project 3 | .settings 4 | *.o 5 | test 6 | rz 7 | cpptest -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ross Bamford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.smblib: -------------------------------------------------------------------------------- 1 | * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html * 2 | * * 3 | * This program is free software; you can redistribute it and/or * 4 | * modify it under the terms of the GNU General Public License * 5 | * as published by the Free Software Foundation; either version 2 * 6 | * of the License, or (at your option) any later version. * 7 | * See the GNU General Public License for more details: gpl.txt or * 8 | * http://www.fsf.org/copyleft/gpl.html * 9 | * * 10 | * Anonymous FTP access to the most recent released source is available at * 11 | * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * 12 | * * 13 | * Anonymous CVS access to the development source and modification history * 14 | * is available at cvs.synchro.net:/cvsroot/sbbs, example: * 15 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * 16 | * (just hit return, no password is necessary) * 17 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * 18 | * * 19 | * For Synchronet coding style and modification guidelines, see * 20 | * http://www.synchro.net/source.html * 21 | * * 22 | * You are encouraged to submit any modifications (preferably in Unix diff * 23 | * format) via e-mail to mods@synchro.net * 24 | * * 25 | * Note: If this box doesn't appear square, then you need to fix your tabs. * 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | LD=gcc 3 | CCP=g++ 4 | LDP=g++ 5 | 6 | CFLAGS=-std=c11 -Wall -Werror -Wpedantic -Iinclude -O3 7 | CPPFLAGS=-std=c++17 -Wall -Werror -Wpedantic -Iinclude -O3 8 | LDFLAGS= 9 | 10 | OBJFILES=zheaders.o znumbers.o zserial.o crc16.o crc32.o 11 | 12 | all: rz test cpptest 13 | 14 | %.o: %.c 15 | $(CC) $(CFLAGS) -c -o $@ $< 16 | 17 | %.o: %.cpp 18 | $(CCP) $(CPPFLAGS) -c -o $@ $< 19 | 20 | rz: rz.o $(OBJFILES) 21 | $(LD) $(LDFLAGS) $^ -o $@ 22 | 23 | test: tests.c zheaders.c znumbers.c zserial.c crc16.c crc32.c 24 | $(CC) $(CFLAGS) -DTEST -o $@ $^ 25 | ./$@ 26 | 27 | cpptest: cpptest.o $(OBJFILES) 28 | $(LDP) $(LDPFLAGS) $^ -o $@ 29 | 30 | clean: 31 | rm -f *.o rz test 32 | 33 | -------------------------------------------------------------------------------- /Makefile.m68k: -------------------------------------------------------------------------------- 1 | CC=m68k-elf-gcc 2 | LD=m68k-elf-ld 3 | 4 | CFLAGS=-std=c11 -Wall -Werror -Wpedantic -Iinclude -O3 -ffreestanding -nostartfiles 5 | OBJFILES=zheaders.o znumbers.o zserial.o crc16.o crc32.o 6 | 7 | all: $(OBJFILES) 8 | 9 | %.o: %.c 10 | $(CC) $(CFLAGS) -DZEMBEDDED -c -o $@ $< 11 | 12 | clean: 13 | rm -f *.o rz 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mbzm - eMBedded ZModem or something... 2 | 3 | A small, simple, modern(ish) and incomplete ZMODEM implementation, for use in resource-constrained 4 | environments, or wherever you can find a use for it really. 5 | 6 | ## Zmodem? Really?? 7 | 8 | Yes, I am aware that it is the twenty-first century, and ZMODEM is hardly the state of the art. 9 | I wrote this for two reasons: 10 | 11 | * I wanted a fun, period-appropriate protocol I could use to load 12 | programs into my retro computer (https://github.com/roscopeco/rosco_m68k) without 13 | having to pull the ROM chips for each iteration. 14 | 15 | * I haven't implemented Zmodem before and wanted to explore how it worked. 16 | 17 | As far as possible, I avoided referring to other ZMODEM implementations and just tried to 18 | implement this using the spec (http://pauillac.inria.fr/~doligez/zmodem/zmodem.txt). 19 | The spec is a bit vague on certain things so I spent quite a bit of time reading old 20 | usenet and forum posts, experimenting, and occasionally peeking at the original `zm.c` 21 | implementation to see how things were supposed to work. 22 | 23 | ## Features/Limitations 24 | 25 | Right now, this is very limited. For one thing, it can only receive. For another, it doesn't deal 26 | with errors in a completely correct way (it _does_ however do the minimum needed to get the 27 | other end to resend bad packets). 28 | 29 | Expanding it to support sending probably wouldn't be all that much work, but I don't need it 30 | right now so I haven't done it. 31 | 32 | Improving the error handling is a WIP, but it's 'good enough' for my purposes right now 33 | (usage with a 1980's UART). 34 | 35 | Other notable things: 36 | 37 | * It doesn't do any memory allocation, so it can be used where malloc is unavailable. 38 | * It's _sort-of_ optimised for use in 16/32-bit environments (I wrote it with M68010 as the primary target) 39 | * It doesn't support XON/XOFF flow control (it does enough that it _might_ work with it, but it's not tested 40 | and it certainly won't shut up when XOFF tells it to!). 41 | * It doesn't support **any** of the advanced features of the protocol (compression etc) 42 | 43 | Additionally, the included sample has even more limitations, such as: 44 | 45 | * It ignores most of the file information in block 0 (it only takes notice of the filename, not the size, mode, etc) 46 | * Related to the above, it doesn't have any support for rejecting files that are too large! 47 | * It doesn't support the (optional) ZSINIT frame and will just ignore it 48 | * It doesn't support resume 49 | * It has a ton of other limitations I'm too lazy to list right now... 50 | * ... but it does work for the simple case of receiving data. 51 | 52 | Note that this last set aren't limitations of the library _per se_ - it's more that they 53 | aren't provided by the library, and the example program doesn't implement them either. 54 | 55 | ## Usage 56 | 57 | ### Tests 58 | 59 | There are some unit tests included. They don't cover everything, but they're a start, and 60 | are likely an improvement over some of the other 30+-year-old code out there. 61 | 62 | `make test` 63 | 64 | ### Sample application 65 | 66 | A sample application is included that will receive a file. You can use `sz` or `minicom` or 67 | something to send a file, probably using `socat` or similar to set up a virtual link. 68 | 69 | E.g. start socat: 70 | 71 | `socat -d -d pty,raw,echo=0 pty,raw,echo=0` 72 | 73 | You'll probably need to change the source to open the correct device. 74 | 75 | To build the application, just do: 76 | 77 | `make` 78 | 79 | and then run it: 80 | 81 | `./rz ` 82 | 83 | Now you can use e.g. `sz` to send a file to it: 84 | 85 | `sz /path/to/somefile > /dev/pts/1 < /dev/pts/1` 86 | 87 | **Be aware** that the sample will blindly overwrite files in the current directory 88 | if it receives a file with the same name! 89 | 90 | ### Use as a library 91 | 92 | To use, you'll need to implement two functions in your code: 93 | 94 | ```c 95 | ZRESULT zm_recv(); 96 | ZRESULT zm_send(uint8_t chr); 97 | ``` 98 | 99 | These should return one of the codes used in the rest of the library 100 | to indicate an error if one occurs (see `ztypes.h` for error codes). 101 | 102 | If there isn't a problem, `recv` should return the next byte from the serial 103 | link. `send` should return `OK`. 104 | 105 | The `ZRESULT` type encodes various things depending on the result of the 106 | function. `ztypes.h` defines a few macros that can help with decoding these 107 | results (e.g. `IS_ERROR`, `IS_FIN`, `ZVALUE` etc). 108 | 109 | #### Cross-compiling 110 | 111 | If using this as part of a larger project, you'll probably want to just pull 112 | the source into your existing build. If, for some reason, you just want to 113 | build the objects for m68k, and you have a cross-compiler, you can do: 114 | 115 | `make -f Makefile.m68k` 116 | 117 | I use this mostly for testing that changes will still build using my cross 118 | toolchain - it probably isn't actually useful for anything... 119 | 120 | When cross-compiling, especially for a freestanding environment, you might want 121 | to define `ZEMBEDDED` as this will stop the library pulling in any stdlib 122 | dependencies. In this case, you're expected to provide two functions: 123 | 124 | * `memset` (I might provide a naive default implementation of this later) 125 | * `strcmp` (this will go away in future) 126 | 127 | ### Debug/Trace Output 128 | 129 | If you define `ZDEBUG` and/or `ZTRACE` when compiling, you can get 130 | a **lot** of output from the library as things happen. This can be useful 131 | if things aren't going your way, but they do require a `printf`. 132 | 133 | If you don't have a `printf` but have something similar, edit the 134 | defines at the bottom of `ztypes.c` to suit. 135 | 136 | ## Shoutouts 137 | 138 | The CRC calculation code comes from the `pkg-sbbs` project on Github 139 | (https://github.com/ftnapps/pkg-sbbs), and is licensed under the GNU GPL. 140 | Because that code works well, is fast, and I have zero interest in calculating 141 | CRCs myself I took the liberty of grabbing the code and reusing it here. 142 | 143 | That code is copyright (c) 2000 Rob Swindell. See the included `LICENSE.smblib` 144 | for license details. 145 | 146 | And obviously much respect to the late Chuck Fosberg, without whom 147 | there would be no protocol to implement. 148 | 149 | -------------------------------------------------------------------------------- /cpptest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * This is just here to test the lib can be compiled with C++ 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #include 17 | 18 | #include "zmodem.h" 19 | 20 | extern "C" ZRESULT zm_send(uint8_t) { return OK; } 21 | extern "C" ZRESULT zm_recv() { return CLOSED; } 22 | 23 | static ZHDR hdr; 24 | static uint8_t buffer[HEX_HDR_STR_LEN + 1]; 25 | 26 | static ZRESULT init_hdr_buf(ZHDR *hdr, uint8_t *buf) { 27 | buf[HEX_HDR_STR_LEN-1] = 0; 28 | zm_calc_hdr_crc(hdr); 29 | return zm_to_hex_header(hdr, buf, HEX_HDR_STR_LEN); 30 | } 31 | 32 | int main() { 33 | hdr.type = ZFIN; 34 | ZRESULT result = init_hdr_buf(&hdr, buffer); 35 | 36 | if (result == HEX_HDR_STR_LEN) { 37 | std::cout << "Struct initialized: " << buffer << std::endl; 38 | } else { 39 | std::cerr << "Something went wrong; " << result << std::endl; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crc16.c: -------------------------------------------------------------------------------- 1 | /* crc16.c */ 2 | 3 | /* CCITT 16-bit CRC table and calculation function */ 4 | 5 | /* $Id: crc16.c,v 1.3 2004/02/27 02:30:36 rswindell Exp $ */ 6 | 7 | /**************************************************************************** 8 | * @format.tab-size 4 (Plain Text/Source Code File Header) * 9 | * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * 10 | * * 11 | * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html * 12 | * * 13 | * This program is free software; you can redistribute it and/or * 14 | * modify it under the terms of the GNU General Public License * 15 | * as published by the Free Software Foundation; either version 2 * 16 | * of the License, or (at your option) any later version. * 17 | * See the GNU General Public License for more details: gpl.txt or * 18 | * http://www.fsf.org/copyleft/gpl.html * 19 | * * 20 | * Anonymous FTP access to the most recent released source is available at * 21 | * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * 22 | * * 23 | * Anonymous CVS access to the development source and modification history * 24 | * is available at cvs.synchro.net:/cvsroot/sbbs, example: * 25 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * 26 | * (just hit return, no password is necessary) * 27 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * 28 | * * 29 | * For Synchronet coding style and modification guidelines, see * 30 | * http://www.synchro.net/source.html * 31 | * * 32 | * You are encouraged to submit any modifications (preferably in Unix diff * 33 | * format) via e-mail to mods@synchro.net * 34 | * * 35 | * Note: If this box doesn't appear square, then you need to fix your tabs. * 36 | ****************************************************************************/ 37 | 38 | #ifdef ZEMBEDDED 39 | #include "embedded.h" 40 | #else 41 | #include /* strlen */ 42 | #endif 43 | #include "crc16.h" 44 | 45 | unsigned short crc16tbl[] = { 46 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 47 | 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 48 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 49 | 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 50 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 51 | 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 52 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 53 | 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 54 | 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 55 | 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 56 | 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 57 | 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 58 | 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 59 | 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 60 | 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 61 | 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 62 | 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 63 | 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 64 | 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 65 | 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 66 | 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 67 | 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 68 | 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 69 | 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 70 | 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 71 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 72 | 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 73 | 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 74 | 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 75 | 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 76 | 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 77 | 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 78 | }; 79 | 80 | unsigned short crc16(char* data, unsigned long len) 81 | { 82 | unsigned short crc = 0; 83 | unsigned long l; 84 | 85 | if(len==0 && data!=NULL) 86 | len=strlen(data); 87 | for(l=0;l /* strlen */ 42 | #endif 43 | #include "crc32.h" 44 | 45 | long crc32tbl[]={ /* CRC polynomial 0xedb88320 */ 46 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 47 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 48 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 49 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 50 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 51 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 52 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 53 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 54 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 55 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 56 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 57 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 58 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 59 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 60 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 61 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 62 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 63 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 64 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 65 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 66 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 67 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 68 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 69 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 70 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 71 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 72 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 73 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 74 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 75 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 76 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 77 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 78 | }; 79 | 80 | /****************************************************************************/ 81 | /* Returns CRC-32 of sequence of bytes (binary or ASCIIZ) */ 82 | /* Pass len of 0 to auto-determine ASCIIZ string length */ 83 | /* or non-zero for arbitrary binary data */ 84 | /****************************************************************************/ 85 | unsigned long crc32i(unsigned long crc, char *buf, unsigned long len) 86 | { 87 | unsigned long l; 88 | 89 | if(len==0 && buf!=NULL) 90 | len=strlen(buf); 91 | for(l=0;l 4 | * 5 | * Copyright 2013-2020 Martin Mitas 6 | * Copyright 2019 Garrett D'Amore 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the "Software"), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the 13 | * Software is furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #ifndef ACUTEST_H__ 28 | #define ACUTEST_H__ 29 | 30 | 31 | /************************ 32 | *** Public interface *** 33 | ************************/ 34 | 35 | /* By default, "acutest.h" provides the main program entry point (function 36 | * main()). However, if the test suite is composed of multiple source files 37 | * which include "acutest.h", then this causes a problem of multiple main() 38 | * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all 39 | * compilation units but one. 40 | */ 41 | 42 | /* Macro to specify list of unit tests in the suite. 43 | * The unit test implementation MUST provide list of unit tests it implements 44 | * with this macro: 45 | * 46 | * TEST_LIST = { 47 | * { "test1_name", test1_func_ptr }, 48 | * { "test2_name", test2_func_ptr }, 49 | * ... 50 | * { 0 } 51 | * }; 52 | * 53 | * The list specifies names of each test (must be unique) and pointer to 54 | * a function implementing it. The function does not take any arguments 55 | * and has no return values, i.e. every test function has to be compatible 56 | * with this prototype: 57 | * 58 | * void test_func(void); 59 | */ 60 | #define TEST_LIST const struct test__ test_list__[] 61 | 62 | 63 | /* Macros for testing whether an unit test succeeds or fails. These macros 64 | * can be used arbitrarily in functions implementing the unit tests. 65 | * 66 | * If any condition fails throughout execution of a test, the test fails. 67 | * 68 | * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows 69 | * also to specify an error message to print out if the condition fails. 70 | * (It expects printf-like format string and its parameters). The macros 71 | * return non-zero (condition passes) or 0 (condition fails). 72 | * 73 | * That can be useful when more conditions should be checked only if some 74 | * preceding condition passes, as illustrated in this code snippet: 75 | * 76 | * SomeStruct* ptr = allocate_some_struct(); 77 | * if(TEST_CHECK(ptr != NULL)) { 78 | * TEST_CHECK(ptr->member1 < 100); 79 | * TEST_CHECK(ptr->member2 > 200); 80 | * } 81 | */ 82 | #define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__) 83 | #define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond) 84 | 85 | 86 | /* These macros are the same as TEST_CHECK_ and TEST_CHECK except that if the 87 | * condition fails, the currently executed unit test is immediately aborted. 88 | * 89 | * That is done either by calling abort() if the unit test is executed as a 90 | * child process; or via longjmp() if the unit test is executed within the 91 | * main Acutest process. 92 | * 93 | * As a side effect of such abortion, your unit tests may cause memory leaks, 94 | * unflushed file descriptors, and other fenomena caused by the abortion. 95 | * 96 | * Therefore you should not use these as a general replacement for TEST_CHECK. 97 | * Use it with some caution, especially if your test causes some other side 98 | * effects to the outside world (e.g. communicating with some server, inserting 99 | * into a database etc.). 100 | */ 101 | #define TEST_ASSERT_(cond,...) \ 102 | do { \ 103 | if (!test_check__((cond), __FILE__, __LINE__, __VA_ARGS__)) \ 104 | test_abort__(); \ 105 | } while(0) 106 | #define TEST_ASSERT(cond) \ 107 | do { \ 108 | if (!test_check__((cond), __FILE__, __LINE__, "%s", #cond)) \ 109 | test_abort__(); \ 110 | } while(0) 111 | 112 | 113 | #ifdef __cplusplus 114 | /* Macros to verify that the code (the 1st argument) throws exception of given 115 | * type (the 2nd argument). (Note these macros are only available in C++.) 116 | * 117 | * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like 118 | * message. 119 | * 120 | * For example: 121 | * 122 | * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType); 123 | * 124 | * If the function_that_throw() throws ExpectedExceptionType, the check passes. 125 | * If the function throws anything incompatible with ExpectedExceptionType 126 | * (or if it does not thrown an exception at all), the check fails. 127 | */ 128 | #define TEST_EXCEPTION(code, exctype) \ 129 | do { \ 130 | bool exc_ok__ = false; \ 131 | const char *msg__ = NULL; \ 132 | try { \ 133 | code; \ 134 | msg__ = "No exception thrown."; \ 135 | } catch(exctype const&) { \ 136 | exc_ok__= true; \ 137 | } catch(...) { \ 138 | msg__ = "Unexpected exception thrown."; \ 139 | } \ 140 | test_check__(exc_ok__, __FILE__, __LINE__, #code " throws " #exctype); \ 141 | if(msg__ != NULL) \ 142 | test_message__("%s", msg__); \ 143 | } while(0) 144 | #define TEST_EXCEPTION_(code, exctype, ...) \ 145 | do { \ 146 | bool exc_ok__ = false; \ 147 | const char *msg__ = NULL; \ 148 | try { \ 149 | code; \ 150 | msg__ = "No exception thrown."; \ 151 | } catch(exctype const&) { \ 152 | exc_ok__= true; \ 153 | } catch(...) { \ 154 | msg__ = "Unexpected exception thrown."; \ 155 | } \ 156 | test_check__(exc_ok__, __FILE__, __LINE__, __VA_ARGS__); \ 157 | if(msg__ != NULL) \ 158 | test_message__("%s", msg__); \ 159 | } while(0) 160 | #endif /* #ifdef __cplusplus */ 161 | 162 | 163 | /* Sometimes it is useful to split execution of more complex unit tests to some 164 | * smaller parts and associate those parts with some names. 165 | * 166 | * This is especially handy if the given unit test is implemented as a loop 167 | * over some vector of multiple testing inputs. Using these macros allow to use 168 | * sort of subtitle for each iteration of the loop (e.g. outputting the input 169 | * itself or a name associated to it), so that if any TEST_CHECK condition 170 | * fails in the loop, it can be easily seen which iteration triggers the 171 | * failure, without the need to manually output the iteration-specific data in 172 | * every single TEST_CHECK inside the loop body. 173 | * 174 | * TEST_CASE allows to specify only single string as the name of the case, 175 | * TEST_CASE_ provides all the power of printf-like string formatting. 176 | * 177 | * Note that the test cases cannot be nested. Starting a new test case ends 178 | * implicitly the previous one. To end the test case explicitly (e.g. to end 179 | * the last test case after exiting the loop), you may use TEST_CASE(NULL). 180 | */ 181 | #define TEST_CASE_(...) test_case__(__VA_ARGS__) 182 | #define TEST_CASE(name) test_case__("%s", name) 183 | 184 | 185 | /* printf-like macro for outputting an extra information about a failure. 186 | * 187 | * Intended use is to output some computed output versus the expected value, 188 | * e.g. like this: 189 | * 190 | * if(!TEST_CHECK(produced == expected)) { 191 | * TEST_MSG("Expected: %d", expected); 192 | * TEST_MSG("Produced: %d", produced); 193 | * } 194 | * 195 | * Note the message is only written down if the most recent use of any checking 196 | * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed. 197 | * This means the above is equivalent to just this: 198 | * 199 | * TEST_CHECK(produced == expected); 200 | * TEST_MSG("Expected: %d", expected); 201 | * TEST_MSG("Produced: %d", produced); 202 | * 203 | * The macro can deal with multi-line output fairly well. It also automatically 204 | * adds a final new-line if there is none present. 205 | */ 206 | #define TEST_MSG(...) test_message__(__VA_ARGS__) 207 | 208 | 209 | /* Maximal output per TEST_MSG call. Longer messages are cut. 210 | * You may define another limit prior including "acutest.h" 211 | */ 212 | #ifndef TEST_MSG_MAXSIZE 213 | #define TEST_MSG_MAXSIZE 1024 214 | #endif 215 | 216 | 217 | /* Macro for dumping a block of memory. 218 | * 219 | * Its intended use is very similar to what TEST_MSG is for, but instead of 220 | * generating any printf-like message, this is for dumping raw block of a 221 | * memory in a hexadecimal form: 222 | * 223 | * TEST_CHECK(size_produced == size_expected && memcmp(addr_produced, addr_expected, size_produced) == 0); 224 | * TEST_DUMP("Expected:", addr_expected, size_expected); 225 | * TEST_DUMP("Produced:", addr_produced, size_produced); 226 | */ 227 | #define TEST_DUMP(title, addr, size) test_dump__(title, addr, size) 228 | 229 | /* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut. 230 | * You may define another limit prior including "acutest.h" 231 | */ 232 | #ifndef TEST_DUMP_MAXSIZE 233 | #define TEST_DUMP_MAXSIZE 1024 234 | #endif 235 | 236 | 237 | /********************** 238 | *** Implementation *** 239 | **********************/ 240 | 241 | /* The unit test files should not rely on anything below. */ 242 | 243 | #include 244 | #include 245 | #include 246 | #include 247 | #include 248 | #include 249 | 250 | #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) 251 | #define ACUTEST_UNIX__ 1 252 | #include 253 | #include 254 | #include 255 | #include 256 | #include 257 | #include 258 | #include 259 | 260 | #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC 261 | #define ACUTEST_HAS_POSIX_TIMER__ 1 262 | #endif 263 | #endif 264 | 265 | #if defined(__gnu_linux__) 266 | #define ACUTEST_LINUX__ 1 267 | #include 268 | #include 269 | #endif 270 | 271 | #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) 272 | #define ACUTEST_WIN__ 1 273 | #include 274 | #include 275 | #endif 276 | 277 | #ifdef __cplusplus 278 | #include 279 | #endif 280 | 281 | 282 | /* Note our global private identifiers end with '__' to mitigate risk of clash 283 | * with the unit tests implementation. */ 284 | 285 | 286 | #ifdef __cplusplus 287 | extern "C" { 288 | #endif 289 | 290 | 291 | struct test__ { 292 | const char* name; 293 | void (*func)(void); 294 | }; 295 | 296 | struct test_detail__ { 297 | unsigned char flags; 298 | double duration; 299 | }; 300 | 301 | enum { 302 | TEST_FLAG_RUN__ = 1 << 0, 303 | TEST_FLAG_SUCCESS__ = 1 << 1, 304 | TEST_FLAG_FAILURE__ = 1 << 2, 305 | }; 306 | 307 | extern const struct test__ test_list__[]; 308 | 309 | int test_check__(int cond, const char* file, int line, const char* fmt, ...); 310 | void test_case__(const char* fmt, ...); 311 | void test_message__(const char* fmt, ...); 312 | void test_dump__(const char* title, const void* addr, size_t size); 313 | void test_abort__(void); 314 | 315 | 316 | #ifndef TEST_NO_MAIN 317 | 318 | static char* test_argv0__ = NULL; 319 | static size_t test_list_size__ = 0; 320 | static struct test_detail__ *test_details__ = NULL; 321 | static size_t test_count__ = 0; 322 | static int test_no_exec__ = -1; 323 | static int test_no_summary__ = 0; 324 | static int test_tap__ = 0; 325 | static int test_skip_mode__ = 0; 326 | static int test_worker__ = 0; 327 | static int test_worker_index__ = 0; 328 | static int test_cond_failed__ = 0; 329 | static int test_was_aborted__ = 0; 330 | static FILE *test_xml_output__ = NULL; 331 | 332 | static int test_stat_failed_units__ = 0; 333 | static int test_stat_run_units__ = 0; 334 | 335 | static const struct test__* test_current_unit__ = NULL; 336 | static int test_current_index__ = 0; 337 | static char test_case_name__[64] = ""; 338 | static int test_current_already_logged__ = 0; 339 | static int test_case_current_already_logged__ = 0; 340 | static int test_verbose_level__ = 2; 341 | static int test_current_failures__ = 0; 342 | static int test_colorize__ = 0; 343 | static int test_timer__ = 0; 344 | 345 | static int test_abort_has_jmp_buf__ = 0; 346 | static jmp_buf test_abort_jmp_buf__; 347 | 348 | #if defined ACUTEST_WIN__ 349 | typedef LARGE_INTEGER test_timer_type__; 350 | static LARGE_INTEGER test_timer_freq__; 351 | static test_timer_type__ test_timer_start__; 352 | static test_timer_type__ test_timer_end__; 353 | 354 | static void 355 | test_timer_init__(void) 356 | { 357 | QueryPerformanceFrequency(&test_timer_freq__); 358 | } 359 | 360 | static void 361 | test_timer_get_time__(LARGE_INTEGER* ts) 362 | { 363 | QueryPerformanceCounter(ts); 364 | } 365 | 366 | static double 367 | test_timer_diff__(LARGE_INTEGER start, LARGE_INTEGER end) 368 | { 369 | double duration = (double)(end.QuadPart - start.QuadPart); 370 | duration /= (double)test_timer_freq__.QuadPart; 371 | return duration; 372 | } 373 | 374 | static void 375 | test_timer_print_diff__(void) 376 | { 377 | printf("%.6lf secs", test_timer_diff__(test_timer_start__, test_timer_end__)); 378 | } 379 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 380 | static clockid_t test_timer_id__; 381 | typedef struct timespec test_timer_type__; 382 | static test_timer_type__ test_timer_start__; 383 | static test_timer_type__ test_timer_end__; 384 | 385 | static void 386 | test_timer_init__(void) 387 | { 388 | if(test_timer__ == 1) 389 | test_timer_id__ = CLOCK_MONOTONIC; 390 | else if(test_timer__ == 2) 391 | test_timer_id__ = CLOCK_PROCESS_CPUTIME_ID; 392 | } 393 | 394 | static void 395 | test_timer_get_time__(struct timespec* ts) 396 | { 397 | clock_gettime(test_timer_id__, ts); 398 | } 399 | 400 | static double 401 | test_timer_diff__(struct timespec start, struct timespec end) 402 | { 403 | double endns; 404 | double startns; 405 | 406 | endns = end.tv_sec; 407 | endns *= 1e9; 408 | endns += end.tv_nsec; 409 | 410 | startns = start.tv_sec; 411 | startns *= 1e9; 412 | startns += start.tv_nsec; 413 | 414 | return ((endns - startns)/ 1e9); 415 | } 416 | 417 | static void 418 | test_timer_print_diff__(void) 419 | { 420 | printf("%.6lf secs", 421 | test_timer_diff__(test_timer_start__, test_timer_end__)); 422 | } 423 | #else 424 | typedef int test_timer_type__; 425 | static test_timer_type__ test_timer_start__; 426 | static test_timer_type__ test_timer_end__; 427 | 428 | void 429 | test_timer_init__(void) 430 | {} 431 | 432 | static void 433 | test_timer_get_time__(int* ts) 434 | { 435 | (void) ts; 436 | } 437 | 438 | static double 439 | test_timer_diff__(int start, int end) 440 | { 441 | (void) start; 442 | (void) end; 443 | return 0.0; 444 | } 445 | 446 | static void 447 | test_timer_print_diff__(void) 448 | {} 449 | #endif 450 | 451 | #define TEST_COLOR_DEFAULT__ 0 452 | #define TEST_COLOR_GREEN__ 1 453 | #define TEST_COLOR_RED__ 2 454 | #define TEST_COLOR_DEFAULT_INTENSIVE__ 3 455 | #define TEST_COLOR_GREEN_INTENSIVE__ 4 456 | #define TEST_COLOR_RED_INTENSIVE__ 5 457 | 458 | static int 459 | test_print_in_color__(int color, const char* fmt, ...) 460 | { 461 | va_list args; 462 | char buffer[256]; 463 | int n; 464 | 465 | va_start(args, fmt); 466 | vsnprintf(buffer, sizeof(buffer), fmt, args); 467 | va_end(args); 468 | buffer[sizeof(buffer)-1] = '\0'; 469 | 470 | if(!test_colorize__) { 471 | return printf("%s", buffer); 472 | } 473 | 474 | #if defined ACUTEST_UNIX__ 475 | { 476 | const char* col_str; 477 | switch(color) { 478 | case TEST_COLOR_GREEN__: col_str = "\033[0;32m"; break; 479 | case TEST_COLOR_RED__: col_str = "\033[0;31m"; break; 480 | case TEST_COLOR_GREEN_INTENSIVE__: col_str = "\033[1;32m"; break; 481 | case TEST_COLOR_RED_INTENSIVE__: col_str = "\033[1;31m"; break; 482 | case TEST_COLOR_DEFAULT_INTENSIVE__: col_str = "\033[1m"; break; 483 | default: col_str = "\033[0m"; break; 484 | } 485 | printf("%s", col_str); 486 | n = printf("%s", buffer); 487 | printf("\033[0m"); 488 | return n; 489 | } 490 | #elif defined ACUTEST_WIN__ 491 | { 492 | HANDLE h; 493 | CONSOLE_SCREEN_BUFFER_INFO info; 494 | WORD attr; 495 | 496 | h = GetStdHandle(STD_OUTPUT_HANDLE); 497 | GetConsoleScreenBufferInfo(h, &info); 498 | 499 | switch(color) { 500 | case TEST_COLOR_GREEN__: attr = FOREGROUND_GREEN; break; 501 | case TEST_COLOR_RED__: attr = FOREGROUND_RED; break; 502 | case TEST_COLOR_GREEN_INTENSIVE__: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; 503 | case TEST_COLOR_RED_INTENSIVE__: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; 504 | case TEST_COLOR_DEFAULT_INTENSIVE__: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; 505 | default: attr = 0; break; 506 | } 507 | if(attr != 0) 508 | SetConsoleTextAttribute(h, attr); 509 | n = printf("%s", buffer); 510 | SetConsoleTextAttribute(h, info.wAttributes); 511 | return n; 512 | } 513 | #else 514 | n = printf("%s", buffer); 515 | return n; 516 | #endif 517 | } 518 | 519 | static void 520 | test_begin_test_line__(const struct test__* test) 521 | { 522 | if(!test_tap__) { 523 | if(test_verbose_level__ >= 3) { 524 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name); 525 | test_current_already_logged__++; 526 | } else if(test_verbose_level__ >= 1) { 527 | int n; 528 | char spaces[48]; 529 | 530 | n = test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name); 531 | memset(spaces, ' ', sizeof(spaces)); 532 | if(n < (int) sizeof(spaces)) 533 | printf("%.*s", (int) sizeof(spaces) - n, spaces); 534 | } else { 535 | test_current_already_logged__ = 1; 536 | } 537 | } 538 | } 539 | 540 | static void 541 | test_finish_test_line__(int result) 542 | { 543 | if(test_tap__) { 544 | const char* str = (result == 0) ? "ok" : "not ok"; 545 | 546 | printf("%s %u - %s\n", str, test_current_index__ + 1, test_current_unit__->name); 547 | 548 | if(result == 0 && test_timer__) { 549 | printf("# Duration: "); 550 | test_timer_print_diff__(); 551 | printf("\n"); 552 | } 553 | } else { 554 | int color = (result == 0) ? TEST_COLOR_GREEN_INTENSIVE__ : TEST_COLOR_RED_INTENSIVE__; 555 | const char* str = (result == 0) ? "OK" : "FAILED"; 556 | printf("[ "); 557 | test_print_in_color__(color, str); 558 | printf(" ]"); 559 | 560 | if(result == 0 && test_timer__) { 561 | printf(" "); 562 | test_timer_print_diff__(); 563 | } 564 | 565 | printf("\n"); 566 | } 567 | } 568 | 569 | static void 570 | test_line_indent__(int level) 571 | { 572 | static const char spaces[] = " "; 573 | int n = level * 2; 574 | 575 | if(test_tap__ && n > 0) { 576 | n--; 577 | printf("#"); 578 | } 579 | 580 | while(n > 16) { 581 | printf("%s", spaces); 582 | n -= 16; 583 | } 584 | printf("%.*s", n, spaces); 585 | } 586 | 587 | int 588 | test_check__(int cond, const char* file, int line, const char* fmt, ...) 589 | { 590 | const char *result_str; 591 | int result_color; 592 | int verbose_level; 593 | 594 | if(cond) { 595 | result_str = "ok"; 596 | result_color = TEST_COLOR_GREEN__; 597 | verbose_level = 3; 598 | } else { 599 | if(!test_current_already_logged__ && test_current_unit__ != NULL) 600 | test_finish_test_line__(-1); 601 | 602 | result_str = "failed"; 603 | result_color = TEST_COLOR_RED__; 604 | verbose_level = 2; 605 | test_current_failures__++; 606 | test_current_already_logged__++; 607 | } 608 | 609 | if(test_verbose_level__ >= verbose_level) { 610 | va_list args; 611 | 612 | if(!test_case_current_already_logged__ && test_case_name__[0]) { 613 | test_line_indent__(1); 614 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); 615 | test_current_already_logged__++; 616 | test_case_current_already_logged__++; 617 | } 618 | 619 | test_line_indent__(test_case_name__[0] ? 2 : 1); 620 | if(file != NULL) { 621 | #ifdef ACUTEST_WIN__ 622 | const char* lastsep1 = strrchr(file, '\\'); 623 | const char* lastsep2 = strrchr(file, '/'); 624 | if(lastsep1 == NULL) 625 | lastsep1 = file-1; 626 | if(lastsep2 == NULL) 627 | lastsep2 = file-1; 628 | file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; 629 | #else 630 | const char* lastsep = strrchr(file, '/'); 631 | if(lastsep != NULL) 632 | file = lastsep+1; 633 | #endif 634 | printf("%s:%d: Check ", file, line); 635 | } 636 | 637 | va_start(args, fmt); 638 | vprintf(fmt, args); 639 | va_end(args); 640 | 641 | printf("... "); 642 | test_print_in_color__(result_color, result_str); 643 | printf("\n"); 644 | test_current_already_logged__++; 645 | } 646 | 647 | test_cond_failed__ = (cond == 0); 648 | return !test_cond_failed__; 649 | } 650 | 651 | void 652 | test_case__(const char* fmt, ...) 653 | { 654 | va_list args; 655 | 656 | if(test_verbose_level__ < 2) 657 | return; 658 | 659 | if(test_case_name__[0]) { 660 | test_case_current_already_logged__ = 0; 661 | test_case_name__[0] = '\0'; 662 | } 663 | 664 | if(fmt == NULL) 665 | return; 666 | 667 | va_start(args, fmt); 668 | vsnprintf(test_case_name__, sizeof(test_case_name__) - 1, fmt, args); 669 | va_end(args); 670 | test_case_name__[sizeof(test_case_name__) - 1] = '\0'; 671 | 672 | if(test_verbose_level__ >= 3) { 673 | test_line_indent__(1); 674 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); 675 | test_current_already_logged__++; 676 | test_case_current_already_logged__++; 677 | } 678 | } 679 | 680 | void 681 | test_message__(const char* fmt, ...) 682 | { 683 | char buffer[TEST_MSG_MAXSIZE]; 684 | char* line_beg; 685 | char* line_end; 686 | va_list args; 687 | 688 | if(test_verbose_level__ < 2) 689 | return; 690 | 691 | /* We allow extra message only when something is already wrong in the 692 | * current test. */ 693 | if(test_current_unit__ == NULL || !test_cond_failed__) 694 | return; 695 | 696 | va_start(args, fmt); 697 | vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args); 698 | va_end(args); 699 | buffer[TEST_MSG_MAXSIZE-1] = '\0'; 700 | 701 | line_beg = buffer; 702 | while(1) { 703 | line_end = strchr(line_beg, '\n'); 704 | if(line_end == NULL) 705 | break; 706 | test_line_indent__(test_case_name__[0] ? 3 : 2); 707 | printf("%.*s\n", (int)(line_end - line_beg), line_beg); 708 | line_beg = line_end + 1; 709 | } 710 | if(line_beg[0] != '\0') { 711 | test_line_indent__(test_case_name__[0] ? 3 : 2); 712 | printf("%s\n", line_beg); 713 | } 714 | } 715 | 716 | void 717 | test_dump__(const char* title, const void* addr, size_t size) 718 | { 719 | static const size_t BYTES_PER_LINE = 16; 720 | size_t line_beg; 721 | size_t truncate = 0; 722 | 723 | if(test_verbose_level__ < 2) 724 | return; 725 | 726 | /* We allow extra message only when something is already wrong in the 727 | * current test. */ 728 | if(test_current_unit__ == NULL || !test_cond_failed__) 729 | return; 730 | 731 | if(size > TEST_DUMP_MAXSIZE) { 732 | truncate = size - TEST_DUMP_MAXSIZE; 733 | size = TEST_DUMP_MAXSIZE; 734 | } 735 | 736 | test_line_indent__(test_case_name__[0] ? 3 : 2); 737 | printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title); 738 | 739 | for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) { 740 | size_t line_end = line_beg + BYTES_PER_LINE; 741 | size_t off; 742 | 743 | test_line_indent__(test_case_name__[0] ? 4 : 3); 744 | printf("%08lx: ", (unsigned long)line_beg); 745 | for(off = line_beg; off < line_end; off++) { 746 | if(off < size) 747 | printf(" %02x", ((unsigned char*)addr)[off]); 748 | else 749 | printf(" "); 750 | } 751 | 752 | printf(" "); 753 | for(off = line_beg; off < line_end; off++) { 754 | unsigned char byte = ((unsigned char*)addr)[off]; 755 | if(off < size) 756 | printf("%c", (iscntrl(byte) ? '.' : byte)); 757 | else 758 | break; 759 | } 760 | 761 | printf("\n"); 762 | } 763 | 764 | if(truncate > 0) { 765 | test_line_indent__(test_case_name__[0] ? 4 : 3); 766 | printf(" ... (and more %u bytes)\n", (unsigned) truncate); 767 | } 768 | } 769 | 770 | void 771 | test_abort__(void) 772 | { 773 | if(test_abort_has_jmp_buf__) 774 | longjmp(test_abort_jmp_buf__, 1); 775 | else 776 | abort(); 777 | } 778 | 779 | static void 780 | test_list_names__(void) 781 | { 782 | const struct test__* test; 783 | 784 | printf("Unit tests:\n"); 785 | for(test = &test_list__[0]; test->func != NULL; test++) 786 | printf(" %s\n", test->name); 787 | } 788 | 789 | static void 790 | test_remember__(int i) 791 | { 792 | if(test_details__[i].flags & TEST_FLAG_RUN__) 793 | return; 794 | 795 | test_details__[i].flags |= TEST_FLAG_RUN__; 796 | test_count__++; 797 | } 798 | 799 | static void 800 | test_set_success__(int i, int success) 801 | { 802 | test_details__[i].flags |= success ? TEST_FLAG_SUCCESS__ : TEST_FLAG_FAILURE__; 803 | } 804 | 805 | static void 806 | test_set_duration__(int i, double duration) 807 | { 808 | test_details__[i].duration = duration; 809 | } 810 | 811 | static int 812 | test_name_contains_word__(const char* name, const char* pattern) 813 | { 814 | static const char word_delim[] = " \t-_."; 815 | const char* substr; 816 | size_t pattern_len; 817 | int starts_on_word_boundary; 818 | int ends_on_word_boundary; 819 | 820 | pattern_len = strlen(pattern); 821 | 822 | substr = strstr(name, pattern); 823 | while(substr != NULL) { 824 | starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL); 825 | ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL); 826 | 827 | if(starts_on_word_boundary && ends_on_word_boundary) 828 | return 1; 829 | 830 | substr = strstr(substr+1, pattern); 831 | } 832 | 833 | return 0; 834 | } 835 | 836 | static int 837 | test_lookup__(const char* pattern) 838 | { 839 | int i; 840 | int n = 0; 841 | 842 | /* Try exact match. */ 843 | for(i = 0; i < (int) test_list_size__; i++) { 844 | if(strcmp(test_list__[i].name, pattern) == 0) { 845 | test_remember__(i); 846 | n++; 847 | break; 848 | } 849 | } 850 | if(n > 0) 851 | return n; 852 | 853 | /* Try word match. */ 854 | for(i = 0; i < (int) test_list_size__; i++) { 855 | if(test_name_contains_word__(test_list__[i].name, pattern)) { 856 | test_remember__(i); 857 | n++; 858 | } 859 | } 860 | if(n > 0) 861 | return n; 862 | 863 | /* Try relaxed match. */ 864 | for(i = 0; i < (int) test_list_size__; i++) { 865 | if(strstr(test_list__[i].name, pattern) != NULL) { 866 | test_remember__(i); 867 | n++; 868 | } 869 | } 870 | 871 | return n; 872 | } 873 | 874 | 875 | /* Called if anything goes bad in Acutest, or if the unit test ends in other 876 | * way then by normal returning from its function (e.g. exception or some 877 | * abnormal child process termination). */ 878 | static void 879 | test_error__(const char* fmt, ...) 880 | { 881 | va_list args; 882 | 883 | if(test_verbose_level__ == 0) 884 | return; 885 | 886 | if(test_verbose_level__ >= 2) { 887 | test_line_indent__(1); 888 | if(test_verbose_level__ >= 3) 889 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "ERROR: "); 890 | va_start(args, fmt); 891 | vprintf(fmt, args); 892 | va_end(args); 893 | printf("\n"); 894 | } 895 | 896 | if(test_verbose_level__ >= 3) { 897 | printf("\n"); 898 | } 899 | } 900 | 901 | /* Call directly the given test unit function. */ 902 | static int 903 | test_do_run__(const struct test__* test, int index) 904 | { 905 | test_was_aborted__ = 0; 906 | test_current_unit__ = test; 907 | test_current_index__ = index; 908 | test_current_failures__ = 0; 909 | test_current_already_logged__ = 0; 910 | test_cond_failed__ = 0; 911 | 912 | test_begin_test_line__(test); 913 | 914 | #ifdef __cplusplus 915 | try { 916 | #endif 917 | 918 | /* This is good to do for case the test unit e.g. crashes. */ 919 | fflush(stdout); 920 | fflush(stderr); 921 | 922 | if(!test_worker__) { 923 | test_abort_has_jmp_buf__ = 1; 924 | if(setjmp(test_abort_jmp_buf__) != 0) { 925 | test_was_aborted__ = 1; 926 | goto aborted; 927 | } 928 | } 929 | 930 | test_timer_get_time__(&test_timer_start__); 931 | test->func(); 932 | aborted: 933 | test_abort_has_jmp_buf__ = 0; 934 | test_timer_get_time__(&test_timer_end__); 935 | 936 | if(test_verbose_level__ >= 3) { 937 | test_line_indent__(1); 938 | if(test_current_failures__ == 0) { 939 | test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS: "); 940 | printf("All conditions have passed.\n"); 941 | 942 | if(test_timer__) { 943 | test_line_indent__(1); 944 | printf("Duration: "); 945 | test_timer_print_diff__(); 946 | printf("\n"); 947 | } 948 | } else { 949 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: "); 950 | if(!test_was_aborted__) { 951 | printf("%d condition%s %s failed.\n", 952 | test_current_failures__, 953 | (test_current_failures__ == 1) ? "" : "s", 954 | (test_current_failures__ == 1) ? "has" : "have"); 955 | } else { 956 | printf("Aborted.\n"); 957 | } 958 | } 959 | printf("\n"); 960 | } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) { 961 | test_finish_test_line__(0); 962 | } 963 | 964 | test_case__(NULL); 965 | test_current_unit__ = NULL; 966 | return (test_current_failures__ == 0) ? 0 : -1; 967 | 968 | #ifdef __cplusplus 969 | } catch(std::exception& e) { 970 | const char* what = e.what(); 971 | test_check__(0, NULL, 0, "Threw std::exception"); 972 | if(what != NULL) 973 | test_message__("std::exception::what(): %s", what); 974 | 975 | if(test_verbose_level__ >= 3) { 976 | test_line_indent__(1); 977 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: "); 978 | printf("C++ exception.\n\n"); 979 | } 980 | 981 | return -1; 982 | } catch(...) { 983 | test_check__(0, NULL, 0, "Threw an exception"); 984 | 985 | if(test_verbose_level__ >= 3) { 986 | test_line_indent__(1); 987 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: "); 988 | printf("C++ exception.\n\n"); 989 | } 990 | 991 | return -1; 992 | } 993 | #endif 994 | } 995 | 996 | /* Trigger the unit test. If possible (and not suppressed) it starts a child 997 | * process who calls test_do_run__(), otherwise it calls test_do_run__() 998 | * directly. */ 999 | static void 1000 | test_run__(const struct test__* test, int index, int master_index) 1001 | { 1002 | int failed = 1; 1003 | test_timer_type__ start, end; 1004 | 1005 | test_current_unit__ = test; 1006 | test_current_already_logged__ = 0; 1007 | test_timer_get_time__(&start); 1008 | 1009 | if(!test_no_exec__) { 1010 | 1011 | #if defined(ACUTEST_UNIX__) 1012 | 1013 | pid_t pid; 1014 | int exit_code; 1015 | 1016 | /* Make sure the child starts with empty I/O buffers. */ 1017 | fflush(stdout); 1018 | fflush(stderr); 1019 | 1020 | pid = fork(); 1021 | if(pid == (pid_t)-1) { 1022 | test_error__("Cannot fork. %s [%d]", strerror(errno), errno); 1023 | failed = 1; 1024 | } else if(pid == 0) { 1025 | /* Child: Do the test. */ 1026 | test_worker__ = 1; 1027 | failed = (test_do_run__(test, index) != 0); 1028 | exit(failed ? 1 : 0); 1029 | } else { 1030 | /* Parent: Wait until child terminates and analyze its exit code. */ 1031 | waitpid(pid, &exit_code, 0); 1032 | if(WIFEXITED(exit_code)) { 1033 | switch(WEXITSTATUS(exit_code)) { 1034 | case 0: failed = 0; break; /* test has passed. */ 1035 | case 1: /* noop */ break; /* "normal" failure. */ 1036 | default: test_error__("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); 1037 | } 1038 | } else if(WIFSIGNALED(exit_code)) { 1039 | char tmp[32]; 1040 | const char* signame; 1041 | switch(WTERMSIG(exit_code)) { 1042 | case SIGINT: signame = "SIGINT"; break; 1043 | case SIGHUP: signame = "SIGHUP"; break; 1044 | case SIGQUIT: signame = "SIGQUIT"; break; 1045 | case SIGABRT: signame = "SIGABRT"; break; 1046 | case SIGKILL: signame = "SIGKILL"; break; 1047 | case SIGSEGV: signame = "SIGSEGV"; break; 1048 | case SIGILL: signame = "SIGILL"; break; 1049 | case SIGTERM: signame = "SIGTERM"; break; 1050 | default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; 1051 | } 1052 | test_error__("Test interrupted by %s.", signame); 1053 | } else { 1054 | test_error__("Test ended in an unexpected way [%d].", exit_code); 1055 | } 1056 | } 1057 | 1058 | #elif defined(ACUTEST_WIN__) 1059 | 1060 | char buffer[512] = {0}; 1061 | STARTUPINFOA startupInfo; 1062 | PROCESS_INFORMATION processInfo; 1063 | DWORD exitCode; 1064 | 1065 | /* Windows has no fork(). So we propagate all info into the child 1066 | * through a command line arguments. */ 1067 | _snprintf(buffer, sizeof(buffer)-1, 1068 | "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", 1069 | test_argv0__, index, test_timer__ ? "--time" : "", 1070 | test_tap__ ? "--tap" : "", test_verbose_level__, 1071 | test_colorize__ ? "always" : "never", 1072 | test->name); 1073 | memset(&startupInfo, 0, sizeof(startupInfo)); 1074 | startupInfo.cb = sizeof(STARTUPINFO); 1075 | if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { 1076 | WaitForSingleObject(processInfo.hProcess, INFINITE); 1077 | GetExitCodeProcess(processInfo.hProcess, &exitCode); 1078 | CloseHandle(processInfo.hThread); 1079 | CloseHandle(processInfo.hProcess); 1080 | failed = (exitCode != 0); 1081 | if(exitCode > 1) { 1082 | switch(exitCode) { 1083 | case 3: test_error__("Aborted."); break; 1084 | case 0xC0000005: test_error__("Access violation."); break; 1085 | default: test_error__("Test ended in an unexpected way [%lu].", exitCode); break; 1086 | } 1087 | } 1088 | } else { 1089 | test_error__("Cannot create unit test subprocess [%ld].", GetLastError()); 1090 | failed = 1; 1091 | } 1092 | 1093 | #else 1094 | 1095 | /* A platform where we don't know how to run child process. */ 1096 | failed = (test_do_run__(test, index) != 0); 1097 | 1098 | #endif 1099 | 1100 | } else { 1101 | /* Child processes suppressed through --no-exec. */ 1102 | failed = (test_do_run__(test, index) != 0); 1103 | } 1104 | test_timer_get_time__(&end); 1105 | 1106 | test_current_unit__ = NULL; 1107 | 1108 | test_stat_run_units__++; 1109 | if(failed) 1110 | test_stat_failed_units__++; 1111 | 1112 | test_set_success__(master_index, !failed); 1113 | test_set_duration__(master_index, test_timer_diff__(start, end)); 1114 | } 1115 | 1116 | #if defined(ACUTEST_WIN__) 1117 | /* Callback for SEH events. */ 1118 | static LONG CALLBACK 1119 | test_seh_exception_filter__(EXCEPTION_POINTERS *ptrs) 1120 | { 1121 | test_check__(0, NULL, 0, "Unhandled SEH exception"); 1122 | test_message__("Exception code: 0x%08lx", ptrs->ExceptionRecord->ExceptionCode); 1123 | test_message__("Exception address: 0x%p", ptrs->ExceptionRecord->ExceptionAddress); 1124 | 1125 | fflush(stdout); 1126 | fflush(stderr); 1127 | 1128 | return EXCEPTION_EXECUTE_HANDLER; 1129 | } 1130 | #endif 1131 | 1132 | 1133 | #define TEST_CMDLINE_OPTFLAG_OPTIONALARG__ 0x0001 1134 | #define TEST_CMDLINE_OPTFLAG_REQUIREDARG__ 0x0002 1135 | 1136 | #define TEST_CMDLINE_OPTID_NONE__ 0 1137 | #define TEST_CMDLINE_OPTID_UNKNOWN__ (-0x7fffffff + 0) 1138 | #define TEST_CMDLINE_OPTID_MISSINGARG__ (-0x7fffffff + 1) 1139 | #define TEST_CMDLINE_OPTID_BOGUSARG__ (-0x7fffffff + 2) 1140 | 1141 | typedef struct TEST_CMDLINE_OPTION__ { 1142 | char shortname; 1143 | const char* longname; 1144 | int id; 1145 | unsigned flags; 1146 | } TEST_CMDLINE_OPTION__; 1147 | 1148 | static int 1149 | test_cmdline_handle_short_opt_group__(const TEST_CMDLINE_OPTION__* options, 1150 | const char* arggroup, 1151 | int (*callback)(int /*optval*/, const char* /*arg*/)) 1152 | { 1153 | const TEST_CMDLINE_OPTION__* opt; 1154 | int i; 1155 | int ret = 0; 1156 | 1157 | for(i = 0; arggroup[i] != '\0'; i++) { 1158 | for(opt = options; opt->id != 0; opt++) { 1159 | if(arggroup[i] == opt->shortname) 1160 | break; 1161 | } 1162 | 1163 | if(opt->id != 0 && !(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { 1164 | ret = callback(opt->id, NULL); 1165 | } else { 1166 | /* Unknown option. */ 1167 | char badoptname[3]; 1168 | badoptname[0] = '-'; 1169 | badoptname[1] = arggroup[i]; 1170 | badoptname[2] = '\0'; 1171 | ret = callback((opt->id != 0 ? TEST_CMDLINE_OPTID_MISSINGARG__ : TEST_CMDLINE_OPTID_UNKNOWN__), 1172 | badoptname); 1173 | } 1174 | 1175 | if(ret != 0) 1176 | break; 1177 | } 1178 | 1179 | return ret; 1180 | } 1181 | 1182 | #define TEST_CMDLINE_AUXBUF_SIZE__ 32 1183 | 1184 | static int 1185 | test_cmdline_read__(const TEST_CMDLINE_OPTION__* options, int argc, char** argv, 1186 | int (*callback)(int /*optval*/, const char* /*arg*/)) 1187 | { 1188 | 1189 | const TEST_CMDLINE_OPTION__* opt; 1190 | char auxbuf[TEST_CMDLINE_AUXBUF_SIZE__+1]; 1191 | int after_doubledash = 0; 1192 | int i = 1; 1193 | int ret = 0; 1194 | 1195 | auxbuf[TEST_CMDLINE_AUXBUF_SIZE__] = '\0'; 1196 | 1197 | while(i < argc) { 1198 | if(after_doubledash || strcmp(argv[i], "-") == 0) { 1199 | /* Non-option argument. */ 1200 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1201 | } else if(strcmp(argv[i], "--") == 0) { 1202 | /* End of options. All the remaining members are non-option arguments. */ 1203 | after_doubledash = 1; 1204 | } else if(argv[i][0] != '-') { 1205 | /* Non-option argument. */ 1206 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1207 | } else { 1208 | for(opt = options; opt->id != 0; opt++) { 1209 | if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { 1210 | size_t len = strlen(opt->longname); 1211 | if(strncmp(argv[i]+2, opt->longname, len) == 0) { 1212 | /* Regular long option. */ 1213 | if(argv[i][2+len] == '\0') { 1214 | /* with no argument provided. */ 1215 | if(!(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) 1216 | ret = callback(opt->id, NULL); 1217 | else 1218 | ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); 1219 | break; 1220 | } else if(argv[i][2+len] == '=') { 1221 | /* with an argument provided. */ 1222 | if(opt->flags & (TEST_CMDLINE_OPTFLAG_OPTIONALARG__ | TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { 1223 | ret = callback(opt->id, argv[i]+2+len+1); 1224 | } else { 1225 | sprintf(auxbuf, "--%s", opt->longname); 1226 | ret = callback(TEST_CMDLINE_OPTID_BOGUSARG__, auxbuf); 1227 | } 1228 | break; 1229 | } else { 1230 | continue; 1231 | } 1232 | } 1233 | } else if(opt->shortname != '\0' && argv[i][0] == '-') { 1234 | if(argv[i][1] == opt->shortname) { 1235 | /* Regular short option. */ 1236 | if(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__) { 1237 | if(argv[i][2] != '\0') 1238 | ret = callback(opt->id, argv[i]+2); 1239 | else if(i+1 < argc) 1240 | ret = callback(opt->id, argv[++i]); 1241 | else 1242 | ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); 1243 | break; 1244 | } else { 1245 | ret = callback(opt->id, NULL); 1246 | 1247 | /* There might be more (argument-less) short options 1248 | * grouped together. */ 1249 | if(ret == 0 && argv[i][2] != '\0') 1250 | ret = test_cmdline_handle_short_opt_group__(options, argv[i]+2, callback); 1251 | break; 1252 | } 1253 | } 1254 | } 1255 | } 1256 | 1257 | if(opt->id == 0) { /* still not handled? */ 1258 | if(argv[i][0] != '-') { 1259 | /* Non-option argument. */ 1260 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1261 | } else { 1262 | /* Unknown option. */ 1263 | char* badoptname = argv[i]; 1264 | 1265 | if(strncmp(badoptname, "--", 2) == 0) { 1266 | /* Strip any argument from the long option. */ 1267 | char* assignment = strchr(badoptname, '='); 1268 | if(assignment != NULL) { 1269 | size_t len = assignment - badoptname; 1270 | if(len > TEST_CMDLINE_AUXBUF_SIZE__) 1271 | len = TEST_CMDLINE_AUXBUF_SIZE__; 1272 | strncpy(auxbuf, badoptname, len); 1273 | auxbuf[len] = '\0'; 1274 | badoptname = auxbuf; 1275 | } 1276 | } 1277 | 1278 | ret = callback(TEST_CMDLINE_OPTID_UNKNOWN__, badoptname); 1279 | } 1280 | } 1281 | } 1282 | 1283 | if(ret != 0) 1284 | return ret; 1285 | i++; 1286 | } 1287 | 1288 | return ret; 1289 | } 1290 | 1291 | static void 1292 | test_help__(void) 1293 | { 1294 | printf("Usage: %s [options] [test...]\n", test_argv0__); 1295 | printf("\n"); 1296 | printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); 1297 | printf("tests in the suite but those listed. By default, if no tests are specified\n"); 1298 | printf("on the command line, all unit tests in the suite are run.\n"); 1299 | printf("\n"); 1300 | printf("Options:\n"); 1301 | printf(" -s, --skip Execute all unit tests but the listed ones\n"); 1302 | printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); 1303 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1304 | printf(" -E, --no-exec Same as --exec=never\n"); 1305 | #if defined ACUTEST_WIN__ 1306 | printf(" -t, --time Measure test duration\n"); 1307 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 1308 | printf(" -t, --time Measure test duration (real time)\n"); 1309 | printf(" --time=TIMER Measure test duration, using given timer\n"); 1310 | printf(" (TIMER is one of 'real', 'cpu')\n"); 1311 | #endif 1312 | printf(" --no-summary Suppress printing of test results summary\n"); 1313 | printf(" --tap Produce TAP-compliant output\n"); 1314 | printf(" (See https://testanything.org/)\n"); 1315 | printf(" -x, --xml-output=FILE Enable XUnit output to the given file\n"); 1316 | printf(" -l, --list List unit tests in the suite and exit\n"); 1317 | printf(" -v, --verbose Make output more verbose\n"); 1318 | printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); 1319 | printf(" 0 ... Be silent\n"); 1320 | printf(" 1 ... Output one line per test (and summary)\n"); 1321 | printf(" 2 ... As 1 and failed conditions (this is default)\n"); 1322 | printf(" 3 ... As 1 and all conditions (and extended summary)\n"); 1323 | printf(" -q, --quiet Same as --verbose=0\n"); 1324 | printf(" --color[=WHEN] Enable colorized output\n"); 1325 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1326 | printf(" --no-color Same as --color=never\n"); 1327 | printf(" -h, --help Display this help and exit\n"); 1328 | 1329 | if(test_list_size__ < 16) { 1330 | printf("\n"); 1331 | test_list_names__(); 1332 | } 1333 | } 1334 | 1335 | static const TEST_CMDLINE_OPTION__ test_cmdline_options__[] = { 1336 | { 's', "skip", 's', 0 }, 1337 | { 0, "exec", 'e', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1338 | { 'E', "no-exec", 'E', 0 }, 1339 | #if defined ACUTEST_WIN__ 1340 | { 't', "time", 't', 0 }, 1341 | { 0, "timer", 't', 0 }, /* kept for compatibility */ 1342 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 1343 | { 't', "time", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1344 | { 0, "timer", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, /* kept for compatibility */ 1345 | #endif 1346 | { 0, "no-summary", 'S', 0 }, 1347 | { 0, "tap", 'T', 0 }, 1348 | { 'l', "list", 'l', 0 }, 1349 | { 'v', "verbose", 'v', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1350 | { 'q', "quiet", 'q', 0 }, 1351 | { 0, "color", 'c', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1352 | { 0, "no-color", 'C', 0 }, 1353 | { 'h', "help", 'h', 0 }, 1354 | { 0, "worker", 'w', TEST_CMDLINE_OPTFLAG_REQUIREDARG__ }, /* internal */ 1355 | { 'x', "xml-output", 'x', TEST_CMDLINE_OPTFLAG_REQUIREDARG__ }, 1356 | { 0, NULL, 0, 0 } 1357 | }; 1358 | 1359 | static int 1360 | test_cmdline_callback__(int id, const char* arg) 1361 | { 1362 | switch(id) { 1363 | case 's': 1364 | test_skip_mode__ = 1; 1365 | break; 1366 | 1367 | case 'e': 1368 | if(arg == NULL || strcmp(arg, "always") == 0) { 1369 | test_no_exec__ = 0; 1370 | } else if(strcmp(arg, "never") == 0) { 1371 | test_no_exec__ = 1; 1372 | } else if(strcmp(arg, "auto") == 0) { 1373 | /*noop*/ 1374 | } else { 1375 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", test_argv0__, arg); 1376 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1377 | exit(2); 1378 | } 1379 | break; 1380 | 1381 | case 'E': 1382 | test_no_exec__ = 1; 1383 | break; 1384 | 1385 | case 't': 1386 | #if defined ACUTEST_WIN__ || defined ACUTEST_HAS_POSIX_TIMER__ 1387 | if(arg == NULL || strcmp(arg, "real") == 0) { 1388 | test_timer__ = 1; 1389 | #ifndef ACUTEST_WIN__ 1390 | } else if(strcmp(arg, "cpu") == 0) { 1391 | test_timer__ = 2; 1392 | #endif 1393 | } else { 1394 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --time.\n", test_argv0__, arg); 1395 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1396 | exit(2); 1397 | } 1398 | #endif 1399 | break; 1400 | 1401 | case 'S': 1402 | test_no_summary__ = 1; 1403 | break; 1404 | 1405 | case 'T': 1406 | test_tap__ = 1; 1407 | break; 1408 | 1409 | case 'l': 1410 | test_list_names__(); 1411 | exit(0); 1412 | 1413 | case 'v': 1414 | test_verbose_level__ = (arg != NULL ? atoi(arg) : test_verbose_level__+1); 1415 | break; 1416 | 1417 | case 'q': 1418 | test_verbose_level__ = 0; 1419 | break; 1420 | 1421 | case 'c': 1422 | if(arg == NULL || strcmp(arg, "always") == 0) { 1423 | test_colorize__ = 1; 1424 | } else if(strcmp(arg, "never") == 0) { 1425 | test_colorize__ = 0; 1426 | } else if(strcmp(arg, "auto") == 0) { 1427 | /*noop*/ 1428 | } else { 1429 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", test_argv0__, arg); 1430 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1431 | exit(2); 1432 | } 1433 | break; 1434 | 1435 | case 'C': 1436 | test_colorize__ = 0; 1437 | break; 1438 | 1439 | case 'h': 1440 | test_help__(); 1441 | exit(0); 1442 | 1443 | case 'w': 1444 | test_worker__ = 1; 1445 | test_worker_index__ = atoi(arg); 1446 | break; 1447 | case 'x': 1448 | test_xml_output__ = fopen(arg, "w"); 1449 | if (!test_xml_output__) { 1450 | fprintf(stderr, "Unable to open '%s': %s\n", arg, strerror(errno)); 1451 | exit(2); 1452 | } 1453 | break; 1454 | 1455 | case 0: 1456 | if(test_lookup__(arg) == 0) { 1457 | fprintf(stderr, "%s: Unrecognized unit test '%s'\n", test_argv0__, arg); 1458 | fprintf(stderr, "Try '%s --list' for list of unit tests.\n", test_argv0__); 1459 | exit(2); 1460 | } 1461 | break; 1462 | 1463 | case TEST_CMDLINE_OPTID_UNKNOWN__: 1464 | fprintf(stderr, "Unrecognized command line option '%s'.\n", arg); 1465 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1466 | exit(2); 1467 | 1468 | case TEST_CMDLINE_OPTID_MISSINGARG__: 1469 | fprintf(stderr, "The command line option '%s' requires an argument.\n", arg); 1470 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1471 | exit(2); 1472 | 1473 | case TEST_CMDLINE_OPTID_BOGUSARG__: 1474 | fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg); 1475 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1476 | exit(2); 1477 | } 1478 | 1479 | return 0; 1480 | } 1481 | 1482 | 1483 | #ifdef ACUTEST_LINUX__ 1484 | static int 1485 | test_is_tracer_present__(void) 1486 | { 1487 | char buf[256+32+1]; 1488 | int tracer_present = 0; 1489 | int fd; 1490 | ssize_t n_read; 1491 | 1492 | fd = open("/proc/self/status", O_RDONLY); 1493 | if(fd == -1) 1494 | return 0; 1495 | 1496 | n_read = read(fd, buf, sizeof(buf)-1); 1497 | while(n_read > 0) { 1498 | static const char pattern[] = "TracerPid:"; 1499 | const char* field; 1500 | 1501 | buf[n_read] = '\0'; 1502 | field = strstr(buf, pattern); 1503 | if(field != NULL && field < buf + sizeof(buf) - 32) { 1504 | pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); 1505 | tracer_present = (tracer_pid != 0); 1506 | break; 1507 | } 1508 | 1509 | if(n_read == sizeof(buf)-1) { 1510 | memmove(buf, buf + sizeof(buf)-1 - 32, 32); 1511 | n_read = read(fd, buf+32, sizeof(buf)-1-32); 1512 | if(n_read > 0) 1513 | n_read += 32; 1514 | } 1515 | } 1516 | 1517 | close(fd); 1518 | return tracer_present; 1519 | } 1520 | #endif 1521 | 1522 | int 1523 | main(int argc, char** argv) 1524 | { 1525 | int i; 1526 | test_argv0__ = argv[0]; 1527 | 1528 | #if defined ACUTEST_UNIX__ 1529 | test_colorize__ = isatty(STDOUT_FILENO); 1530 | #elif defined ACUTEST_WIN__ 1531 | #if defined __BORLANDC__ 1532 | test_colorize__ = isatty(_fileno(stdout)); 1533 | #else 1534 | test_colorize__ = _isatty(_fileno(stdout)); 1535 | #endif 1536 | #else 1537 | test_colorize__ = 0; 1538 | #endif 1539 | 1540 | /* Count all test units */ 1541 | test_list_size__ = 0; 1542 | for(i = 0; test_list__[i].func != NULL; i++) 1543 | test_list_size__++; 1544 | 1545 | test_details__ = (struct test_detail__*)calloc(test_list_size__, sizeof(struct test_detail__)); 1546 | if(test_details__ == NULL) { 1547 | fprintf(stderr, "Out of memory.\n"); 1548 | exit(2); 1549 | } 1550 | 1551 | /* Parse options */ 1552 | test_cmdline_read__(test_cmdline_options__, argc, argv, test_cmdline_callback__); 1553 | 1554 | /* Initialize the proper timer. */ 1555 | test_timer_init__(); 1556 | 1557 | #if defined(ACUTEST_WIN__) 1558 | SetUnhandledExceptionFilter(test_seh_exception_filter__); 1559 | #endif 1560 | 1561 | /* By default, we want to run all tests. */ 1562 | if(test_count__ == 0) { 1563 | for(i = 0; test_list__[i].func != NULL; i++) 1564 | test_remember__(i); 1565 | } 1566 | 1567 | /* Guess whether we want to run unit tests as child processes. */ 1568 | if(test_no_exec__ < 0) { 1569 | test_no_exec__ = 0; 1570 | 1571 | if(test_count__ <= 1) { 1572 | test_no_exec__ = 1; 1573 | } else { 1574 | #ifdef ACUTEST_WIN__ 1575 | if(IsDebuggerPresent()) 1576 | test_no_exec__ = 1; 1577 | #endif 1578 | #ifdef ACUTEST_LINUX__ 1579 | if(test_is_tracer_present__()) 1580 | test_no_exec__ = 1; 1581 | #endif 1582 | } 1583 | } 1584 | 1585 | if(test_tap__) { 1586 | /* TAP requires we know test result ("ok", "not ok") before we output 1587 | * anything about the test, and this gets problematic for larger verbose 1588 | * levels. */ 1589 | if(test_verbose_level__ > 2) 1590 | test_verbose_level__ = 2; 1591 | 1592 | /* TAP harness should provide some summary. */ 1593 | test_no_summary__ = 1; 1594 | 1595 | if(!test_worker__) 1596 | printf("1..%d\n", (int) test_count__); 1597 | } 1598 | 1599 | int index = test_worker_index__; 1600 | for(i = 0; test_list__[i].func != NULL; i++) { 1601 | int run = (test_details__[i].flags & TEST_FLAG_RUN__); 1602 | if (test_skip_mode__) /* Run all tests except those listed. */ 1603 | run = !run; 1604 | if(run) 1605 | test_run__(&test_list__[i], index++, i); 1606 | } 1607 | 1608 | /* Write a summary */ 1609 | if(!test_no_summary__ && test_verbose_level__ >= 1) { 1610 | if(test_verbose_level__ >= 3) { 1611 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Summary:\n"); 1612 | 1613 | printf(" Count of all unit tests: %4d\n", (int) test_list_size__); 1614 | printf(" Count of run unit tests: %4d\n", test_stat_run_units__); 1615 | printf(" Count of failed unit tests: %4d\n", test_stat_failed_units__); 1616 | printf(" Count of skipped unit tests: %4d\n", (int) test_list_size__ - test_stat_run_units__); 1617 | } 1618 | 1619 | if(test_stat_failed_units__ == 0) { 1620 | test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS:"); 1621 | printf(" All unit tests have passed.\n"); 1622 | } else { 1623 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED:"); 1624 | printf(" %d of %d unit tests %s failed.\n", 1625 | test_stat_failed_units__, test_stat_run_units__, 1626 | (test_stat_failed_units__ == 1) ? "has" : "have"); 1627 | } 1628 | 1629 | if(test_verbose_level__ >= 3) 1630 | printf("\n"); 1631 | } 1632 | 1633 | if (test_xml_output__) { 1634 | #if defined ACUTEST_UNIX__ 1635 | char *suite_name = basename(argv[0]); 1636 | #elif defined ACUTEST_WIN__ 1637 | char suite_name[_MAX_FNAME]; 1638 | _splitpath(argv[0], NULL, NULL, suite_name, NULL); 1639 | #else 1640 | const char *suite_name = argv[0]; 1641 | #endif 1642 | fprintf(test_xml_output__, "\n"); 1643 | fprintf(test_xml_output__, "\n", 1644 | suite_name, (int)test_list_size__, test_stat_failed_units__, test_stat_failed_units__, 1645 | (int)test_list_size__ - test_stat_run_units__); 1646 | for(i = 0; test_list__[i].func != NULL; i++) { 1647 | struct test_detail__ *details = &test_details__[i]; 1648 | fprintf(test_xml_output__, " \n", test_list__[i].name, details->duration); 1649 | if (details->flags & TEST_FLAG_FAILURE__) 1650 | fprintf(test_xml_output__, " \n"); 1651 | if (!(details->flags & TEST_FLAG_FAILURE__) && !(details->flags & TEST_FLAG_SUCCESS__)) 1652 | fprintf(test_xml_output__, " \n"); 1653 | fprintf(test_xml_output__, " \n"); 1654 | } 1655 | fprintf(test_xml_output__, "\n"); 1656 | fclose(test_xml_output__); 1657 | } 1658 | 1659 | free((void*) test_details__); 1660 | 1661 | return (test_stat_failed_units__ == 0) ? 0 : 1; 1662 | } 1663 | 1664 | 1665 | #endif /* #ifndef TEST_NO_MAIN */ 1666 | 1667 | #ifdef __cplusplus 1668 | } /* extern "C" */ 1669 | #endif 1670 | 1671 | 1672 | #endif /* #ifndef ACUTEST_H__ */ 1673 | 1674 | -------------------------------------------------------------------------------- /include/crc16.h: -------------------------------------------------------------------------------- 1 | /* crc16.h */ 2 | 3 | /* CCITT 16-bit CRC table and calculation macro */ 4 | 5 | /* $Id: crc16.h,v 1.2 2003/10/15 02:51:26 rswindell Exp $ */ 6 | 7 | /**************************************************************************** 8 | * @format.tab-size 4 (Plain Text/Source Code File Header) * 9 | * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * 10 | * * 11 | * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html * 12 | * * 13 | * This program is free software; you can redistribute it and/or * 14 | * modify it under the terms of the GNU General Public License * 15 | * as published by the Free Software Foundation; either version 2 * 16 | * of the License, or (at your option) any later version. * 17 | * See the GNU General Public License for more details: gpl.txt or * 18 | * http://www.fsf.org/copyleft/gpl.html * 19 | * * 20 | * Anonymous FTP access to the most recent released source is available at * 21 | * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * 22 | * * 23 | * Anonymous CVS access to the development source and modification history * 24 | * is available at cvs.synchro.net:/cvsroot/sbbs, example: * 25 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * 26 | * (just hit return, no password is necessary) * 27 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * 28 | * * 29 | * For Synchronet coding style and modification guidelines, see * 30 | * http://www.synchro.net/source.html * 31 | * * 32 | * You are encouraged to submit any modifications (preferably in Unix diff * 33 | * format) via e-mail to mods@synchro.net * 34 | * * 35 | * Note: If this box doesn't appear square, then you need to fix your tabs. * 36 | ****************************************************************************/ 37 | 38 | #ifndef _CRC16_H_ 39 | #define _CRC16_H_ 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | extern unsigned short crc16tbl[]; 46 | 47 | unsigned short crc16(char *data, unsigned long len); 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | 53 | #define CRC_START_XMODEM ((uint16_t)0) 54 | 55 | #define ucrc16(ch,crc) (crc16tbl[((crc>>8)&0xff)^(unsigned char)ch]^(crc << 8)) 56 | 57 | #endif /* Don't add anything after this line */ 58 | -------------------------------------------------------------------------------- /include/crc32.h: -------------------------------------------------------------------------------- 1 | /* crc32.h */ 2 | 3 | /* 32-bit CRC table and calculation macro */ 4 | 5 | /* $Id: crc32.h,v 1.11 2006/05/09 18:54:56 deuce Exp $ */ 6 | 7 | /**************************************************************************** 8 | * @format.tab-size 4 (Plain Text/Source Code File Header) * 9 | * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * 10 | * * 11 | * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html * 12 | * * 13 | * This program is free software; you can redistribute it and/or * 14 | * modify it under the terms of the GNU General Public License * 15 | * as published by the Free Software Foundation; either version 2 * 16 | * of the License, or (at your option) any later version. * 17 | * See the GNU General Public License for more details: gpl.txt or * 18 | * http://www.fsf.org/copyleft/gpl.html * 19 | * * 20 | * Anonymous FTP access to the most recent released source is available at * 21 | * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * 22 | * * 23 | * Anonymous CVS access to the development source and modification history * 24 | * is available at cvs.synchro.net:/cvsroot/sbbs, example: * 25 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * 26 | * (just hit return, no password is necessary) * 27 | * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * 28 | * * 29 | * For Synchronet coding style and modification guidelines, see * 30 | * http://www.synchro.net/source.html * 31 | * * 32 | * You are encouraged to submit any modifications (preferably in Unix diff * 33 | * format) via e-mail to mods@synchro.net * 34 | * * 35 | * Note: If this box doesn't appear square, then you need to fix your tabs. * 36 | ****************************************************************************/ 37 | 38 | #ifndef _CRC32_H_ 39 | #define _CRC32_H_ 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | extern long crc32tbl[]; 46 | 47 | unsigned long crc32i(unsigned long crc, char* buf, unsigned long len); 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | 53 | #define CRC_START_32 ((uint32_t)~0) 54 | 55 | #define ucrc32(ch,crc) (crc32tbl[(crc^(ch))&0xff]^(crc>>8)) 56 | #define crc32(x,y) crc32i(CRC_START_32,x,y) 57 | 58 | #endif /* Don't add anything after this line */ 59 | -------------------------------------------------------------------------------- /include/embedded.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Declare stdlib routines when compiling for freestanding envs. 13 | * No implementations are provided.... 14 | * ------------------------------------------------------------ 15 | */ 16 | 17 | #ifndef __ROSCO_M68K_ZEMBEDDED_H 18 | #define __ROSCO_M68K_ZEMBEDDED_H 19 | #ifdef ZEMBEDDED 20 | 21 | #include 22 | 23 | void *memset (void *mem, int val, size_t len); 24 | int strcmp (const char *s1, const char *s2); 25 | int strlen (const char *str); 26 | 27 | #endif 28 | #endif /* __ROSCO_M68K_ZEMBEDDED_H */ 29 | -------------------------------------------------------------------------------- /include/zheaders.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Routines for working with Zmodem headers 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifndef __ROSCO_M68K_ZHEADERS_H 17 | #define __ROSCO_M68K_ZHEADERS_H 18 | 19 | #include 20 | #include "ztypes.h" 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | /* 27 | * One-shot calculate the CRC for a ZHDR and set the 28 | * crc1 and crc2 fields appropriately. 29 | */ 30 | void zm_calc_hdr_crc(ZHDR *hdr); 31 | 32 | uint16_t zm_calc_data_crc(uint8_t *buf, uint16_t len); 33 | uint32_t zm_calc_data_crc32(uint8_t *buf, uint16_t len); 34 | 35 | /* 36 | * Converts ZHDR to wire-format hex header. Expects CRC is already 37 | * computed. Result placed in the supplied buffer. 38 | * 39 | * The encoded header includes the 'B' header-type character and 40 | * trailing CRLF, but does not include other Zmodem control 41 | * characters (e.g. leading ZBUF/ZDLE etc). 42 | * 43 | * Returns actual used length (max 0xff bytes), or OUT_OF_SPACE 44 | * if the supplied buffer is not large enough. 45 | */ 46 | ZRESULT zm_to_hex_header(ZHDR *hdr, uint8_t *buf, int max_len); 47 | 48 | ZRESULT zm_check_header_crc16(ZHDR *hdr, uint16_t crc); 49 | ZRESULT zm_check_header_crc32(ZHDR *hdr, uint32_t crc); 50 | 51 | #ifdef ZDEBUG 52 | /* this is wasteful, but only if debugging is on, so, y'know... */ 53 | static char *__hdrtypes[] __attribute__((unused)) = { 54 | "ZRQINIT", "ZRINIT", "ZSINIT", "ZACK", 55 | "ZFILE", "ZSKIP", "ZNAK", "ZABORT", 56 | "ZFIN", "ZRPOS", "ZDATA", "ZEOF", 57 | "ZERR", "ZCRC", "ZCHALLENGE", "ZCOMPL", 58 | "ZCAN", "ZFREECOUNT", "ZCOMMAND", "ZSTDERR" 59 | }; 60 | 61 | #define DEBUG_DUMPHDR_F(hdr) \ 62 | DEBUGF("DEBUG: Header type [%s]:\n", \ 63 | __hdrtypes[hdr->type]); \ 64 | DEBUGF(" type: 0x%02x\n", hdr->type); \ 65 | DEBUGF(" f0: 0x%02x\n", hdr->flags.f0); \ 66 | DEBUGF(" f1: 0x%02x\n", hdr->flags.f1); \ 67 | DEBUGF(" f2: 0x%02x\n", hdr->flags.f2); \ 68 | DEBUGF(" f3: 0x%02x\n", hdr->flags.f3); \ 69 | DEBUGF(" crc1: 0x%02x\n", hdr->crc1); \ 70 | DEBUGF(" crc2: 0x%02x\n", hdr->crc2); \ 71 | DEBUGF(" RES: 0x%02x\n", hdr->PADDING); \ 72 | DEBUGF("\n"); 73 | 74 | #define DEBUG_DUMPHDR_P(hdr) \ 75 | DEBUGF("DEBUG: Header type [%s]:\n", \ 76 | __hdrtypes[hdr->type]); \ 77 | DEBUGF(" type: 0x%02x\n", hdr->type); \ 78 | DEBUGF(" p0: 0x%02x\n", hdr->position.p0); \ 79 | DEBUGF(" p1: 0x%02x\n", hdr->position.p1); \ 80 | DEBUGF(" p2: 0x%02x\n", hdr->position.p2); \ 81 | DEBUGF(" p3: 0x%02x\n", hdr->position.p3); \ 82 | DEBUGF(" crc1: 0x%02x\n", hdr->crc1); \ 83 | DEBUGF(" crc2: 0x%02x\n", hdr->crc2); \ 84 | DEBUGF(" RES: 0x%02x\n", hdr->PADDING); \ 85 | DEBUGF("\n"); 86 | 87 | #define DEBUG_DUMPHDR_R(hdr) \ 88 | DEBUGF("DEBUG: Header received [%s]:\n", \ 89 | __hdrtypes[hdr->type]); \ 90 | DEBUGF(" type: 0x%02x\n", hdr->type); \ 91 | DEBUGF(" p0/f3: 0x%02x\n", hdr->position.p0); \ 92 | DEBUGF(" p1/f2: 0x%02x\n", hdr->position.p1); \ 93 | DEBUGF(" p2/f1: 0x%02x\n", hdr->position.p2); \ 94 | DEBUGF(" p3/f0: 0x%02x\n", hdr->position.p3); \ 95 | DEBUGF(" crc1: 0x%02x\n", hdr->crc1); \ 96 | DEBUGF(" crc2: 0x%02x\n", hdr->crc2); \ 97 | DEBUGF(" crc3: 0x%02x\n", hdr->crc3); \ 98 | DEBUGF(" crc4: 0x%02x\n", hdr->crc4); \ 99 | DEBUGF(" RES: 0x%02x\n", hdr->PADDING); \ 100 | DEBUGF("\n"); 101 | 102 | #define DEBUG_DUMPHDR DEBUG_DUMPHDR_F 103 | #else 104 | #define DEBUG_DUMPHDR_F(hdr) 105 | #define DEBUG_DUMPHDR_P(hdr) 106 | #define DEBUG_DUMPHDR_R(hdr) 107 | #define DEBUG_DUMPHDR(hdr) 108 | #endif 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | #endif /* __ROSCO_M68K_ZHEADERS_H */ 115 | -------------------------------------------------------------------------------- /include/zmodem.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Basic Zmodem implementation for kernel loader. 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifndef __ROSCO_M68K_ZMODEM_H 17 | #define __ROSCO_M68K_ZMODEM_H 18 | 19 | #include "ztypes.h" 20 | #include "znumbers.h" 21 | #include "zheaders.h" 22 | #include "zserial.h" 23 | 24 | #endif /* __ROSCO_M68K_ZMODEM_H */ 25 | -------------------------------------------------------------------------------- /include/znumbers.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Numeric-related routines for Zmodem implementation. 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifndef __ROSCO_M68K_ZNUMBERS_H 17 | #define __ROSCO_M68K_ZNUMBERS_H 18 | 19 | #include 20 | #include "ztypes.h" 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | ZRESULT zm_hex_to_nybble(char c1); 27 | 28 | ZRESULT zm_nybble_to_hex(uint8_t nybble); 29 | 30 | /* 31 | * *buf MUST have space for exactly two characters! 32 | * 33 | * Returns OK on success, or an error code. 34 | * If an error occues, the buffer will be unchanged. 35 | */ 36 | ZRESULT zm_byte_to_hex(uint8_t byte, uint8_t *buf); 37 | 38 | ZRESULT zm_hex_to_byte(unsigned char c1, unsigned char c2); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif /* __ROSCO_M68K_ZNUMBERS_H */ 45 | 46 | -------------------------------------------------------------------------------- /include/zserial.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Generic serial routines for Zmodem 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifndef __ROSCO_M68K_ZSERIAL_H 17 | #define __ROSCO_M68K_ZSERIAL_H 18 | 19 | #include 20 | #include 21 | #include "ztypes.h" 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #define NONCONTROL(c) ((bool)((uint8_t)(c & 0xe0))) 28 | 29 | /* 30 | * The lib doesn't implement these - they need to be provided. 31 | */ 32 | ZRESULT zm_recv(); 33 | ZRESULT zm_send(uint8_t chr); 34 | 35 | /* 36 | * Receive CR/LF (with CR being optional). 37 | */ 38 | ZRESULT zm_read_crlf(); 39 | 40 | /* 41 | * Read two ASCII characters and convert them from hex. 42 | */ 43 | ZRESULT zm_read_hex_byte(); 44 | 45 | /* 46 | * Read character, taking care of ZMODEM Data Link Escapes (ZDLE) 47 | * and swallowing XON/XOFF. 48 | */ 49 | ZRESULT zm_read_escaped(); 50 | 51 | /* 52 | * buf must be one character longer than the string... 53 | * Trashes buf, for obvious reasons. 54 | */ 55 | ZRESULT zm_await(char *str, char *buf, int buf_size); 56 | ZRESULT zm_await_zdle(); 57 | ZRESULT zm_await_header(ZHDR *hdr); 58 | 59 | ZRESULT zm_read_hex_header(ZHDR *hdr); 60 | ZRESULT zm_read_binary16_header(ZHDR *hdr); 61 | ZRESULT zm_read_binary32_header(ZHDR *hdr); 62 | 63 | 64 | /* 65 | * len specifies the maximum length to read on entry, 66 | * and contains actual length on return. 67 | */ 68 | ZRESULT zm_read_data_block(uint8_t *buf, uint16_t *len); 69 | 70 | /* 71 | * Send a null-terminated string. 72 | */ 73 | ZRESULT zm_send_sz(uint8_t *data); 74 | 75 | /* 76 | * Send the given header as hex, with ZPAD/ZDLE preamble. 77 | */ 78 | ZRESULT zm_send_hex_hdr(ZHDR *hdr); 79 | 80 | /* 81 | * Convenience function to build and send a position header as hex. 82 | */ 83 | ZRESULT zm_send_pos_hdr(uint8_t type, uint32_t pos); 84 | 85 | /* 86 | * Convenience function to build and send a flags header as hex. 87 | */ 88 | ZRESULT zm_send_flags_hdr(uint8_t type, uint8_t f0, uint8_t f1, uint8_t f2, uint8_t f3); 89 | 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | 94 | #endif /* __ROSCO_M68K_ZSERIAL_H */ 95 | -------------------------------------------------------------------------------- /include/ztypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Types and defines for Zmodem implementation 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | 17 | #ifndef __ROSCO_M68K_ZTYPES_H 18 | #define __ROSCO_M68K_ZTYPES_H 19 | 20 | #include 21 | #include 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | // ASCII Control characters 28 | #define XON 0x11 29 | #define XOFF 0x13 30 | #define LF 0x0a 31 | #define CR 0x0d 32 | #define ZPAD '*' 33 | #define ZDLE 0x18 /* ZDLE and CAN */ 34 | #define CAN 0x18 /* are the same */ 35 | 36 | // ZDLE Escape sequences 37 | #define ZCRCE 'h' 38 | #define ZCRCG 'i' 39 | #define ZCRCQ 'j' 40 | #define ZCRCW 'k' 41 | #define ZRUB0 'l' 42 | #define ZRUB1 'm' 43 | 44 | // Header types 45 | #define ZBIN16 'A' 46 | #define ZHEX 'B' 47 | #define ZBIN32 'C' 48 | 49 | // Frame types 50 | #define ZRQINIT 0x00 51 | #define ZRINIT 0x01 52 | #define ZSINIT 0x02 53 | #define ZACK 0x03 54 | #define ZFILE 0x04 55 | #define ZSKIP 0x05 56 | #define ZNAK 0x06 57 | #define ZABORT 0x07 58 | #define ZFIN 0x08 59 | #define ZRPOS 0x09 60 | #define ZDATA 0x0a 61 | #define ZEOF 0x0b 62 | #define ZERR 0x0c 63 | #define ZCRC 0x0d 64 | #define ZCHALLENGE 0x0e 65 | #define ZCOMPL 0x0f 66 | #define ZCAN 0x10 67 | #define ZFREECOUNT 0x11 68 | #define ZCOMMAND 0x12 69 | #define ZSTDERR 0x13 70 | 71 | // Capabilities for ZRINIT 72 | #define CANFDX 0x01 /* Rx can send and receive true FDX */ 73 | #define CANOVIO 0x02 /* Rx can receive data during disk I/O */ 74 | #define CANBRK 0x04 /* Rx can send a break signal */ 75 | #define CANCRY 0x08 /* Receiver can decrypt */ 76 | #define CANLZW 0x10 /* Receiver can uncompress */ 77 | #define CANFC32 0x20 /* Receiver can use 32 bit Frame Check */ 78 | #define ESCCTL 0x40 /* Receiver expects ctl chars to be escaped */ 79 | #define ESC8 0x80 /* Receiver expects 8th bit to be escaped */ 80 | 81 | // ZFILE conversion options (F0) 82 | #define ZCBIN 0x01 /* Binary transfer - inhibit conversion */ 83 | #define ZCNL 0x02 /* Convert NL to local end of line convention */ 84 | #define ZCRESUM 0x03 /* Resume interrupted file transfer */ 85 | 86 | // ZRESULT Masks 87 | #define VALUE_MASK 0x00ff /* Mask used to extract value from ZRESULT */ 88 | #define ERROR_MASK 0xf000 /* Mask used to determine if result is an error */ 89 | 90 | // ZRESULT codes - Non-errors 91 | #define OK 0x0100 /* Generic return code for "all is well" */ 92 | #define FIN 0x0200 /* OR with < 0xff to indicate a return condition */ 93 | #define GOT_CRCE (FIN | ZCRCE) /* CRC follows, end of frame, header is next */ 94 | #define GOT_CRCG (FIN | ZCRCG) /* CRC follows, frame continues (non-stop) */ 95 | #define GOT_CRCQ (FIN | ZCRCQ) /* CRC follows, frame continues, ZACK expected */ 96 | #define GOT_CRCW (FIN | ZCRCW) /* CRC follows, end of frame, ZACK expected */ 97 | 98 | // ZRESULT codes - Errors 99 | #define BAD_DIGIT 0x1000 /* Bad digit when converting from hex */ 100 | #define CLOSED 0x2000 /* Got EOF when reading from stream */ 101 | #define BAD_HEADER_TYPE 0x3000 /* Bad header type in stream (probably noise) */ 102 | #define BAD_FRAME_TYPE 0x4000 /* Bad frame type in stream (probably noise) */ 103 | #define CORRUPTED 0x5000 /* Corruption detected in header (probably noise) */ 104 | #define BAD_CRC 0x6000 /* Header did not match CRC (probably noise) */ 105 | #define OUT_OF_RANGE 0x7000 /* Conversion attempted for out-of-range number */ 106 | #define OUT_OF_SPACE 0x8000 /* Supplied buffer is not big enough */ 107 | #define CANCELLED 0x9000 /* 5x CAN received */ 108 | #define BAD_ESCAPE 0xa000 /* Bad escape sequence */ 109 | #define UNSUPPORTED 0xf000 /* Attempted to use an unsupported protocol feature */ 110 | 111 | #define ERROR_CODE(x) (x & ERROR_MASK) 112 | #define IS_ERROR(x) ((bool)(ERROR_CODE((x)) != 0)) 113 | #define IS_FIN(x) ((bool)((x & FIN) == FIN)) 114 | #define ZVALUE(x) ((uint8_t)(x & 0xff)) 115 | #define IS_ZVALUE(x, y) ((bool)((x & 0xff) == y)) 116 | 117 | // Nybble to byte vice-versa 118 | #define NTOB(n1, n2) (n1 << 4 | n2) /* 2 nybbles -> byte */ 119 | #define BMSN(b) ((b & 0xf0) >> 4) /* byte -> most-significant nybble */ 120 | #define BLSN(b) (b & 0x0f) /* byte -> least-significant nybble */ 121 | 122 | // Byte to word / vice-versa 123 | #define BTOW(b1, b2) (b1 << 8 | b2) /* 2 bytes -> word */ 124 | #define WMSB(w) ((w & 0xff00) >> 8) /* word -> most-significant byte */ 125 | #define WLSB(w) (w & 0x00ff) /* word -> least-significant byte */ 126 | 127 | // Byte to dword / vice versa 128 | #define BTODW(b1, b2, b3, b4) (b4 << 24 | b3 << 16 | b2 << 8 | b1) /* 4 bytes -> dword */ 129 | #define DWB1(l) (l & 0x000000ff) /* MSB */ 130 | #define DWB2(l) ((l & 0x0000ff00) >> 8) /* 2rd-MSB */ 131 | #define DWB3(l) ((l & 0x00ff0000) >> 16) /* 3nd-MSB */ 132 | #define DWB4(l) ((l & 0xff000000) >> 24) /* LSB */ 133 | 134 | // CRC manipulation 135 | #define CRC BTOW /* Convert two-bytes to 16-bit CRC */ 136 | #define CRC_MSB WMSB /* Get most-significant byte of 16-bit CRC */ 137 | #define CRC_LSB WLSB /* Get least-significant byte of 16-bit CRC */ 138 | 139 | // CRC32 manipulation 140 | #define CRC32 BTODW /* Convert four bytes to 32-bit CRC */ 141 | #define CRC32_B1 DWB1 /* Most-significant byte of CRC32 */ 142 | #define CRC32_B2 DWB2 /* Second-most-significant byte of CRC32 */ 143 | #define CRC32_B3 DWB3 /* Third-most-significant byte of CRC32 */ 144 | #define CRC32_B4 DWB4 /* Least-significant byte of CRC32 */ 145 | 146 | // Various sizes 147 | #define ZHDR_SIZE 0x09 /* Size of ZHDR (excluding padding) */ 148 | #define HEX_HDR_STR_LEN 0x11 /* Total size of a ZHDR encoded as hex */ 149 | 150 | /* 151 | * The ZRESULT type is the general return type for functions in this library. 152 | * 153 | */ 154 | typedef uint16_t ZRESULT; 155 | 156 | typedef struct { 157 | uint8_t f3; 158 | uint8_t f2; 159 | uint8_t f1; 160 | uint8_t f0; 161 | } ZFLAGS; 162 | 163 | typedef struct { 164 | uint8_t p0; 165 | uint8_t p1; 166 | uint8_t p2; 167 | uint8_t p3; 168 | } ZPOS; 169 | 170 | typedef struct { 171 | uint8_t type; 172 | union { 173 | ZFLAGS flags; 174 | ZPOS position; 175 | }; 176 | uint8_t crc1; /* keep these byte-sized to avoid alignment */ 177 | uint8_t crc2; /* issues with 16-bit reads on m68k */ 178 | uint8_t crc3; 179 | uint8_t crc4; 180 | uint8_t PADDING; 181 | } ZHDR; 182 | 183 | #ifdef ZDEBUG 184 | #define DEBUGF(...) printf(__VA_ARGS__) 185 | #else 186 | #define DEBUGF(...) 187 | #endif 188 | 189 | #ifdef ZTRACE 190 | #define TRACEF(...) printf(__VA_ARGS__) 191 | #else 192 | #define TRACEF(...) 193 | #endif 194 | 195 | #ifdef __cplusplus 196 | } 197 | #endif 198 | 199 | #endif /* __ROSCO_M68K_ZTYPES_H */ 200 | -------------------------------------------------------------------------------- /rz.c: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Example usage of Zmodem implementation 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "zmodem.h" 21 | 22 | #ifdef ZEMBEDDED 23 | #define PRINTF(...) 24 | #define FPRINTF(...) 25 | #else 26 | #define PRINTF(...) printf(__VA_ARGS__) 27 | #define FPRINTF(...) fprintf(__VA_ARGS__) 28 | #endif 29 | 30 | // Spec says a data packet is max 1024 bytes, but add some headroom... 31 | #define DATA_BUF_LEN 2048 32 | 33 | static FILE *com; 34 | 35 | /* 36 | * Implementation-defined receive character function. 37 | */ 38 | ZRESULT zm_recv() { 39 | uint8_t result; 40 | 41 | if (fread(&result, 1, 1, com) == 1) { 42 | TRACEF(" !!!! zm_recv: read [0x%02x]\n", result); 43 | return result; 44 | } else { 45 | DEBUGF("Read in zm_recv returned no data; Closed\n"); 46 | return CLOSED; 47 | } 48 | } 49 | 50 | /* 51 | * Implementation-defined send character function. 52 | */ 53 | ZRESULT zm_send(uint8_t chr) { 54 | register int result = fputc((char)chr, com); 55 | 56 | if (result == EOF) { 57 | return CLOSED; 58 | } else { 59 | return OK; 60 | } 61 | } 62 | 63 | static FILE* init_com(int argc, char **argv) { 64 | if (argc != 2) { 65 | FPRINTF(stderr, "Usage: rz \n"); 66 | return NULL; 67 | } else { 68 | char *fn = argv[1]; 69 | 70 | FPRINTF(stderr, "Opening '%s' as com device\n", fn); 71 | FILE *com = fopen(fn, "wb+"); 72 | 73 | if (com == NULL) { 74 | FPRINTF(stderr, "Failed to open; quitting\n"); 75 | return NULL; 76 | } 77 | 78 | if (setvbuf(com, NULL, _IONBF, 0) != 0) { 79 | FPRINTF(stderr, "Failed to disable buffering; Bailing...\n"); 80 | 81 | if (fclose(com) != 0) { 82 | FPRINTF(stderr, "WARN: File not closed successfully!'n"); 83 | } 84 | 85 | return NULL; 86 | } 87 | 88 | return com; 89 | } 90 | } 91 | 92 | int main(int argc, char **argv) { 93 | uint8_t rzr_buf[4]; 94 | uint8_t data_buf[DATA_BUF_LEN]; 95 | uint16_t count; 96 | uint32_t received_data_size = 0; 97 | ZHDR hdr; 98 | FILE *out = NULL; 99 | 100 | #ifdef ZDEBUG_DUMP_BAD_BLOCKS 101 | uint32_t bad_block_count = 0; 102 | #endif 103 | 104 | if ((com = init_com(argc, argv)) != NULL) { 105 | DEBUGF("Opened port just fine\n"); 106 | 107 | PRINTF("rosco_m68k ZMODEM receive example v0.01 - Awaiting remote transfer initiation...\n"); 108 | 109 | if (zm_await("rz\r", (char*)rzr_buf, 4) == OK) { 110 | DEBUGF("Got rzr...\n"); 111 | 112 | while (true) { 113 | startframe: 114 | DEBUGF("\n====================================\n"); 115 | uint16_t result = zm_await_header(&hdr); 116 | 117 | switch (result) { 118 | case CANCELLED: 119 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 120 | goto cleanup; 121 | case OK: 122 | DEBUGF("Got valid header\n"); 123 | 124 | switch (hdr.type) { 125 | case ZRQINIT: 126 | case ZEOF: 127 | DEBUGF("Is ZRQINIT or ZEOF\n"); 128 | 129 | result = zm_send_flags_hdr(ZRINIT, CANOVIO | CANFC32, 0, 0, 0); 130 | 131 | if (result == CANCELLED) { 132 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 133 | goto cleanup; 134 | } else if (result == OK) { 135 | DEBUGF("Send ZRINIT was OK\n"); 136 | } else if(result == CLOSED) { 137 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 138 | goto cleanup; 139 | } 140 | 141 | continue; 142 | 143 | case ZFIN: 144 | DEBUGF("Is ZFIN\n"); 145 | 146 | result = zm_send_pos_hdr(ZFIN, 0); 147 | 148 | if (result == CANCELLED) { 149 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 150 | goto cleanup; 151 | } else if (result == OK) { 152 | DEBUGF("Send ZFIN was OK\n"); 153 | } else if(result == CLOSED) { 154 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 155 | } 156 | 157 | PRINTF("Transfer complete; Received %0d byte(s)\n", received_data_size); 158 | goto cleanup; 159 | 160 | case ZFILE: 161 | DEBUGF("Is ZFILE\n"); 162 | 163 | switch (hdr.flags.f0) { 164 | case 0: /* no special treatment - default to ZCBIN */ 165 | case ZCBIN: 166 | DEBUGF("--> Binary receive\n"); 167 | break; 168 | case ZCNL: 169 | DEBUGF("--> ASCII Receive; Fix newlines (IGNORED - NOT SUPPORTED)\n"); 170 | break; 171 | case ZCRESUM: 172 | DEBUGF("--> Resume interrupted transfer (IGNORED - NOT SUPPORTED)\n"); 173 | break; 174 | default: 175 | FPRINTF(stderr, "WARN: Invalid conversion flag [0x%02x] (IGNORED - Assuming Binary)\n", hdr.flags.f0); 176 | } 177 | 178 | count = DATA_BUF_LEN; 179 | result = zm_read_data_block(data_buf, &count); 180 | DEBUGF("Result of data block read is [0x%04x] (got %d character(s))\n", result, count); 181 | 182 | if (result == CANCELLED) { 183 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 184 | goto cleanup; 185 | } else if (!IS_ERROR(result)) { 186 | PRINTF("Receiving file: '%s'\n", data_buf); 187 | 188 | out = fopen((char*)data_buf, "wb"); 189 | if (out == NULL) { 190 | FPRINTF(stderr, "Error opening file for output; Bailing...\n"); 191 | goto cleanup; 192 | } 193 | 194 | result = zm_send_pos_hdr(ZRPOS, received_data_size); 195 | 196 | if (result == CANCELLED) { 197 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 198 | goto cleanup; 199 | } else if (result == OK) { 200 | DEBUGF("Send ZRPOS was OK\n"); 201 | } else if(result == CLOSED) { 202 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 203 | goto cleanup; 204 | } 205 | } 206 | 207 | // TODO care about XON that will follow? 208 | 209 | continue; 210 | 211 | case ZDATA: 212 | DEBUGF("Is ZDATA\n"); 213 | 214 | while (true) { 215 | count = DATA_BUF_LEN; 216 | result = zm_read_data_block(data_buf, &count); 217 | DEBUGF("Result of data block read is [0x%04x] (got %d character(s))\n", result, count); 218 | 219 | if (out == NULL) { 220 | FPRINTF(stderr, "Received data before open file; Bailing...\n"); 221 | goto cleanup; 222 | } 223 | 224 | if (result == CANCELLED) { 225 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 226 | goto cleanup; 227 | } else if (!IS_ERROR(result)) { 228 | DEBUGF("Received %d byte(s) of data\n", count); 229 | 230 | fwrite(data_buf, count - 1, 1, out); 231 | received_data_size += (count - 1); 232 | 233 | if (result == GOT_CRCE) { 234 | // End of frame, header follows, no ZACK expected. 235 | DEBUGF("Got CRCE; Frame done [NOACK] [Pos: 0x%08x]\n", received_data_size); 236 | break; 237 | } else if (result == GOT_CRCG) { 238 | // Frame continues, non-stop (another data packet follows) 239 | DEBUGF("Got CRCG; Frame continues [NOACK] [Pos: 0x%08x]\n", received_data_size); 240 | continue; 241 | } else if (result == GOT_CRCQ) { 242 | // Frame continues, ZACK required 243 | DEBUGF("Got CRCQ; Frame continues [ACK] [Pos: 0x%08x]\n", received_data_size); 244 | 245 | result = zm_send_pos_hdr(ZACK, received_data_size); 246 | 247 | if (result == CANCELLED) { 248 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 249 | goto cleanup; 250 | } else if (result == OK) { 251 | DEBUGF("Send ZACK was OK\n"); 252 | } else if(result == CLOSED) { 253 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 254 | goto cleanup; 255 | } 256 | 257 | continue; 258 | } else if (result == GOT_CRCW) { 259 | // End of frame, header follows, ZACK expected. 260 | DEBUGF("Got CRCW; Frame done [ACK] [Pos: 0x%08x]\n", received_data_size); 261 | 262 | result = zm_send_pos_hdr(ZACK, received_data_size); 263 | 264 | if (result == CANCELLED) { 265 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 266 | goto cleanup; 267 | } else if (result == OK) { 268 | DEBUGF("Send ZACK was OK\n"); 269 | } else if(result == CLOSED) { 270 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 271 | goto cleanup; 272 | } 273 | 274 | break; 275 | } 276 | 277 | } else { 278 | DEBUGF("Error while receiving block: 0x%04x\n", result); 279 | 280 | result = zm_send_pos_hdr(ZRPOS, received_data_size); 281 | 282 | #ifdef ZDEBUG_DUMP_BAD_BLOCKS 283 | char name[20]; 284 | snprintf(name, 20, "block%d.bin", bad_block_count++); 285 | DEBUGF(" >> Writing file '%s'\n", name); 286 | FILE *block = fopen(name, "wb"); 287 | fwrite(data_buf,count,1,block); 288 | fclose(block); 289 | #endif 290 | 291 | if (result == CANCELLED) { 292 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 293 | goto cleanup; 294 | } else if (result == OK) { 295 | DEBUGF("Send ZRPOS was OK\n"); 296 | goto startframe; 297 | } else if(result == CLOSED) { 298 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 299 | goto cleanup; 300 | } 301 | } 302 | } 303 | 304 | continue; 305 | 306 | default: 307 | PRINTF("WARN: Ignoring unknown header type 0x%02x\n", hdr.type); 308 | continue; 309 | } 310 | 311 | break; 312 | case BAD_CRC: 313 | DEBUGF("Didn't get valid header - CRC Check failed\n"); 314 | 315 | result = zm_send_pos_hdr(ZNAK, received_data_size); 316 | 317 | if (result == CANCELLED) { 318 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 319 | goto cleanup; 320 | } else if (result == OK) { 321 | DEBUGF("Send ZNACK was OK\n"); 322 | } else if(result == CLOSED) { 323 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 324 | goto cleanup; 325 | } 326 | 327 | continue; 328 | default: 329 | DEBUGF("Didn't get valid header - result is 0x%04x\n", result); 330 | 331 | result = zm_send_pos_hdr(ZNAK, received_data_size); 332 | 333 | if (result == CANCELLED) { 334 | FPRINTF(stderr, "Transfer cancelled by remote; Bailing...\n"); 335 | goto cleanup; 336 | } else if (result == OK) { 337 | DEBUGF("Send ZNACK was OK\n"); 338 | } else if(result == CLOSED) { 339 | FPRINTF(stderr, "Connection closed prematurely; Bailing...\n"); 340 | goto cleanup; 341 | } 342 | 343 | continue; 344 | } 345 | } 346 | } 347 | 348 | cleanup: 349 | 350 | if (out != NULL && fclose(out)) { 351 | FPRINTF(stderr, "Failed to close output file\n"); 352 | } 353 | if (com != NULL && fclose(com)) { 354 | FPRINTF(stderr, "Failed to close serial port\n"); 355 | } 356 | 357 | } else { 358 | PRINTF("Unable to open port\n"); 359 | return 2; 360 | } 361 | } 362 | 363 | 364 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Example and test-harness for Zmodem implementation 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "zmodem.h" 21 | #include "acutest.h" 22 | 23 | #define RECV_LEN 1024 24 | 25 | static char recv_buf[RECV_LEN]; 26 | static char *buf_ptr, *buf_limit; 27 | 28 | /* Set up the fake receive buffer for use in tests */ 29 | uint16_t set_buf(char* buf, int len) { 30 | buf_ptr = buf_limit = recv_buf; 31 | 32 | if (len > RECV_LEN) { 33 | return UNSUPPORTED; 34 | } else { 35 | for (int i = 0; i < len; i++) { 36 | *buf_limit++ = buf[i]; 37 | } 38 | 39 | return OK; 40 | } 41 | } 42 | 43 | /* recv implementation for use in tests */ 44 | ZRESULT zm_recv() { 45 | if (buf_ptr < buf_limit) { 46 | return *buf_ptr++; 47 | } else { 48 | return CLOSED; 49 | } 50 | } 51 | 52 | /* send implementation for use in tests */ 53 | ZRESULT zm_send(uint8_t c) { 54 | return OK; 55 | } 56 | 57 | /* Tests of the tests */ 58 | void test_recv_buffer() { 59 | TEST_CHECK(set_buf("a", 1025) == UNSUPPORTED); 60 | 61 | set_buf("abc", 3); 62 | 63 | TEST_CHECK(zm_recv() == 'a'); 64 | TEST_CHECK(zm_recv() == 'b'); 65 | TEST_CHECK(zm_recv() == 'c'); 66 | 67 | TEST_CHECK(zm_recv() == CLOSED); 68 | TEST_CHECK(zm_recv() == CLOSED); 69 | } 70 | 71 | /* The actual tests */ 72 | void test_is_error() { 73 | TEST_CHECK(IS_ERROR(0x0000) == false); 74 | TEST_CHECK(IS_ERROR(0x0001) == false); 75 | TEST_CHECK(IS_ERROR(0x00ff) == false); 76 | TEST_CHECK(IS_ERROR(0x0f00) == false); 77 | TEST_CHECK(IS_ERROR(0xf000) == true); 78 | TEST_CHECK(IS_ERROR(0xff00) == true); 79 | 80 | TEST_CHECK(IS_ERROR(OK) == false); 81 | TEST_CHECK(IS_ERROR(BAD_DIGIT) == true); 82 | } 83 | 84 | void test_get_error_code() { 85 | TEST_CHECK(ERROR_CODE(0x0000) == 0x0000); 86 | TEST_CHECK(ERROR_CODE(0x0001) == 0x0000); 87 | TEST_CHECK(ERROR_CODE(0x00f0) == 0x0000); 88 | TEST_CHECK(ERROR_CODE(0x00ff) == 0x0000); 89 | TEST_CHECK(ERROR_CODE(0x0f00) == 0x0000); 90 | TEST_CHECK(ERROR_CODE(0xf000) == 0xf000); 91 | TEST_CHECK(ERROR_CODE(0xff00) == 0xf000); 92 | TEST_CHECK(ERROR_CODE(0xffc0) == 0xf000); 93 | } 94 | 95 | void test_zvalue() { 96 | TEST_CHECK(ZVALUE(0x0000) == 0x00); 97 | TEST_CHECK(ZVALUE(0x0001) == 0x01); 98 | TEST_CHECK(ZVALUE(0x1000) == 0x00); 99 | TEST_CHECK(ZVALUE(0xf00d) == 0x0d); 100 | 101 | TEST_CHECK(ZVALUE(GOT_CRCE) == ZCRCE); 102 | TEST_CHECK(ZVALUE(GOT_CRCG) == ZCRCG); 103 | TEST_CHECK(ZVALUE(GOT_CRCQ) == ZCRCQ); 104 | TEST_CHECK(ZVALUE(GOT_CRCW) == ZCRCW); 105 | 106 | } 107 | 108 | void test_noncontrol() { 109 | for (uint8_t i = 0; i < 0xff; i++) { 110 | if (i < 32) { 111 | TEST_CHECK(NONCONTROL(i) == false); 112 | } else { 113 | TEST_CHECK(NONCONTROL(i) == true); 114 | } 115 | } 116 | } 117 | 118 | void test_is_fin() { 119 | TEST_CHECK(IS_FIN(OK) == false); 120 | TEST_CHECK(IS_FIN(BAD_DIGIT) == false); 121 | 122 | TEST_CHECK(IS_FIN(GOT_CRCE) == true); 123 | TEST_CHECK(IS_FIN(GOT_CRCG) == true); 124 | TEST_CHECK(IS_FIN(GOT_CRCQ) == true); 125 | TEST_CHECK(IS_FIN(GOT_CRCW) == true); 126 | } 127 | 128 | void test_hex_to_nybble() { 129 | TEST_CHECK(zm_hex_to_nybble('0') == 0x0); 130 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('0')) == 0); 131 | TEST_CHECK(zm_hex_to_nybble('1') == 0x1); 132 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('1')) == 0); 133 | TEST_CHECK(zm_hex_to_nybble('2') == 0x2); 134 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('2')) == 0); 135 | TEST_CHECK(zm_hex_to_nybble('3') == 0x3); 136 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('3')) == 0); 137 | TEST_CHECK(zm_hex_to_nybble('4') == 0x4); 138 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('4')) == 0); 139 | TEST_CHECK(zm_hex_to_nybble('5') == 0x5); 140 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('5')) == 0); 141 | TEST_CHECK(zm_hex_to_nybble('6') == 0x6); 142 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('6')) == 0); 143 | TEST_CHECK(zm_hex_to_nybble('7') == 0x7); 144 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('7')) == 0); 145 | TEST_CHECK(zm_hex_to_nybble('8') == 0x8); 146 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('8')) == 0); 147 | TEST_CHECK(zm_hex_to_nybble('9') == 0x9); 148 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('9')) == 0); 149 | TEST_CHECK(zm_hex_to_nybble('a') == 0xa); 150 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('a')) == 0); 151 | TEST_CHECK(zm_hex_to_nybble('b') == 0xb); 152 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('b')) == 0); 153 | TEST_CHECK(zm_hex_to_nybble('c') == 0xc); 154 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('c')) == 0); 155 | TEST_CHECK(zm_hex_to_nybble('d') == 0xd); 156 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('d')) == 0); 157 | TEST_CHECK(zm_hex_to_nybble('e') == 0xe); 158 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('e')) == 0); 159 | TEST_CHECK(zm_hex_to_nybble('f') == 0xf); 160 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('f')) == 0); 161 | 162 | TEST_CHECK(ERROR_CODE(zm_hex_to_nybble('A')) == BAD_DIGIT); 163 | } 164 | 165 | void test_hex_to_byte() { 166 | TEST_CHECK(zm_hex_to_byte('0', '0') == 0x00); 167 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('0', '0')) == 0); 168 | TEST_CHECK(zm_hex_to_byte('0', '1') == 0x01); 169 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('0', '1')) == 0); 170 | TEST_CHECK(zm_hex_to_byte('0', 'e') == 0x0e); 171 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('0', 'e')) == 0); 172 | TEST_CHECK(zm_hex_to_byte('0', 'f') == 0x0f); 173 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('0', 'f')) == 0); 174 | TEST_CHECK(zm_hex_to_byte('1', '0') == 0x10); 175 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('1', '0')) == 0); 176 | TEST_CHECK(zm_hex_to_byte('1', '1') == 0x11); 177 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('1', '1')) == 0); 178 | TEST_CHECK(zm_hex_to_byte('1', 'e') == 0x1e); 179 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('1', 'e')) == 0); 180 | TEST_CHECK(zm_hex_to_byte('1', 'f') == 0x1f); 181 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('1', 'f')) == 0); 182 | TEST_CHECK(zm_hex_to_byte('f', '0') == 0xf0); 183 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('f', '0')) == 0); 184 | TEST_CHECK(zm_hex_to_byte('f', '1') == 0xf1); 185 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('f', '1')) == 0); 186 | TEST_CHECK(zm_hex_to_byte('f', 'e') == 0xfe); 187 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('f', 'e')) == 0); 188 | TEST_CHECK(zm_hex_to_byte('f', 'f') == 0xff); 189 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('f', 'f')) == 0); 190 | 191 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('0', 'A')) == BAD_DIGIT); 192 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('A', '0')) == BAD_DIGIT); 193 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('A', 'A')) == BAD_DIGIT); 194 | TEST_CHECK(ERROR_CODE(zm_hex_to_byte('N', 'O')) == BAD_DIGIT); 195 | } 196 | 197 | void test_nybble_to_hex() { 198 | TEST_CHECK(zm_nybble_to_hex(0x0) == '0'); 199 | TEST_CHECK(zm_nybble_to_hex(0x1) == '1'); 200 | TEST_CHECK(zm_nybble_to_hex(0x2) == '2'); 201 | TEST_CHECK(zm_nybble_to_hex(0x3) == '3'); 202 | TEST_CHECK(zm_nybble_to_hex(0x4) == '4'); 203 | TEST_CHECK(zm_nybble_to_hex(0x5) == '5'); 204 | TEST_CHECK(zm_nybble_to_hex(0x6) == '6'); 205 | TEST_CHECK(zm_nybble_to_hex(0x7) == '7'); 206 | TEST_CHECK(zm_nybble_to_hex(0x8) == '8'); 207 | TEST_CHECK(zm_nybble_to_hex(0x9) == '9'); 208 | TEST_CHECK(zm_nybble_to_hex(0xa) == 'a'); 209 | TEST_CHECK(zm_nybble_to_hex(0xb) == 'b'); 210 | TEST_CHECK(zm_nybble_to_hex(0xc) == 'c'); 211 | TEST_CHECK(zm_nybble_to_hex(0xd) == 'd'); 212 | TEST_CHECK(zm_nybble_to_hex(0xe) == 'e'); 213 | TEST_CHECK(zm_nybble_to_hex(0xf) == 'f'); 214 | 215 | TEST_CHECK(IS_ERROR(zm_nybble_to_hex(0x10)) == true); 216 | TEST_CHECK(IS_ERROR(zm_nybble_to_hex(0xFF)) == true); 217 | } 218 | 219 | void test_byte_to_hex() { 220 | uint8_t buf[2]; 221 | 222 | TEST_CHECK(zm_byte_to_hex(0x00, buf) == OK); 223 | TEST_CHECK(buf[0] == '0'); 224 | TEST_CHECK(buf[1] == '0'); 225 | 226 | TEST_CHECK(zm_byte_to_hex(0x01, buf) == OK); 227 | TEST_CHECK(buf[0] == '0'); 228 | TEST_CHECK(buf[1] == '1'); 229 | 230 | TEST_CHECK(zm_byte_to_hex(0x0f, buf) == OK); 231 | TEST_CHECK(buf[0] == '0'); 232 | TEST_CHECK(buf[1] == 'f'); 233 | 234 | TEST_CHECK(zm_byte_to_hex(0x10, buf) == OK); 235 | TEST_CHECK(buf[0] == '1'); 236 | TEST_CHECK(buf[1] == '0'); 237 | 238 | TEST_CHECK(zm_byte_to_hex(0xff, buf) == OK); 239 | TEST_CHECK(buf[0] == 'f'); 240 | TEST_CHECK(buf[1] == 'f'); 241 | } 242 | 243 | void test_read_hex_header() { 244 | ZHDR hdr; 245 | 246 | // All zeros - CRC is zero 247 | set_buf("00000000000000", 14); 248 | TEST_CHECK(zm_read_hex_header(&hdr) == OK); 249 | 250 | TEST_CHECK(hdr.type == 0); 251 | TEST_CHECK(hdr.flags.f0 == 0); 252 | TEST_CHECK(hdr.flags.f1 == 0); 253 | TEST_CHECK(hdr.flags.f2 == 0); 254 | TEST_CHECK(hdr.flags.f3 == 0); 255 | TEST_CHECK(hdr.crc1 == 0); 256 | TEST_CHECK(hdr.crc2 == 0); 257 | 258 | // Correct CRC - 01 02 03 04 05 - CRC is 0x8208 259 | set_buf("01020304058208", 14); 260 | TEST_CHECK(zm_read_hex_header(&hdr) == OK); 261 | 262 | TEST_CHECK(hdr.type == 0x01); 263 | TEST_CHECK(hdr.position.p0 == 0x02); 264 | TEST_CHECK(hdr.position.p1 == 0x03); 265 | TEST_CHECK(hdr.position.p2 == 0x04); 266 | TEST_CHECK(hdr.position.p3 == 0x05); 267 | TEST_CHECK(hdr.flags.f3 == 0x02); 268 | TEST_CHECK(hdr.flags.f2 == 0x03); 269 | TEST_CHECK(hdr.flags.f1 == 0x04); 270 | TEST_CHECK(hdr.flags.f0 == 0x05); 271 | TEST_CHECK(hdr.crc1 == 0x82); 272 | TEST_CHECK(hdr.crc2 == 0x08); 273 | 274 | // Incorrect CRC - 01 02 03 04 05 - CRC is 0x8208, but expect 0xc0c0 275 | // Note that header left intact for debugging 276 | set_buf("0102030405c0c0", 14); 277 | TEST_CHECK(zm_read_hex_header(&hdr) == BAD_CRC); 278 | 279 | TEST_CHECK(hdr.type == 0x01); 280 | TEST_CHECK(hdr.position.p0 == 0x02); 281 | TEST_CHECK(hdr.position.p1 == 0x03); 282 | TEST_CHECK(hdr.position.p2 == 0x04); 283 | TEST_CHECK(hdr.position.p3 == 0x05); 284 | TEST_CHECK(hdr.flags.f3 == 0x02); 285 | TEST_CHECK(hdr.flags.f2 == 0x03); 286 | TEST_CHECK(hdr.flags.f1 == 0x04); 287 | TEST_CHECK(hdr.flags.f0 == 0x05); 288 | TEST_CHECK(hdr.crc1 == 0xc0); 289 | TEST_CHECK(hdr.crc2 == 0xc0); 290 | 291 | // Invalid data - 01 02 0Z 04 05 292 | // Note that header is undefined 293 | set_buf("01020Z0405c0c0", 14); 294 | TEST_CHECK(zm_read_hex_header(&hdr) == BAD_DIGIT); 295 | } 296 | 297 | void test_calc_hdr_crc() { 298 | ZHDR hdr = { 299 | .type = 0x01, 300 | .flags = { 301 | .f3 = 0x02, 302 | .f2 = 0x03, 303 | .f1 = 0x04, 304 | .f0 = 0x05 305 | } 306 | }; 307 | 308 | zm_calc_hdr_crc(&hdr); 309 | 310 | TEST_CHECK(hdr.crc1 == 0x82); 311 | TEST_CHECK(hdr.crc2 == 0x08); 312 | 313 | TEST_CHECK(CRC(hdr.crc1, hdr.crc2) == 0x8208); 314 | 315 | ZHDR real_hdr = { 316 | .type = 0x06, 317 | .flags = { 318 | .f3 = 0x00, 319 | .f2 = 0x00, 320 | .f1 = 0x00, 321 | .f0 = 0x00 322 | } 323 | }; 324 | 325 | zm_calc_hdr_crc(&real_hdr); 326 | 327 | TEST_CHECK(real_hdr.crc1 == 0xcd); 328 | TEST_CHECK(real_hdr.crc2 == 0x85); 329 | 330 | TEST_CHECK(CRC(real_hdr.crc1, real_hdr.crc2) == 0xcd85); 331 | } 332 | 333 | void test_to_hex_header() { 334 | ZHDR hdr = { 335 | .type = 0x01, 336 | .flags = { 337 | .f3 = 0x02, 338 | .f2 = 0x03, 339 | .f1 = 0x04, 340 | .f0 = 0x05, 341 | }, 342 | .crc1 = 0x0a, 343 | .crc2 = 0x0b 344 | }; 345 | 346 | uint8_t buf[0xff]; 347 | memset(buf, 0, 0xff); 348 | 349 | // Too small - buffer not modified 350 | TEST_CHECK(zm_to_hex_header(&hdr, buf, 0x01) == OUT_OF_SPACE); 351 | TEST_CHECK(buf[0] == 0); 352 | 353 | // Still too small - buffer not modified 354 | TEST_CHECK(zm_to_hex_header(&hdr, buf, HEX_HDR_STR_LEN - 1) == OUT_OF_SPACE); 355 | TEST_CHECK(buf[0] == 0); 356 | 357 | // Exactly correct size 358 | TEST_CHECK(zm_to_hex_header(&hdr, buf, HEX_HDR_STR_LEN) == HEX_HDR_STR_LEN); 359 | TEST_CHECK(strcmp("B01020304050a0b\r\x8a", (const char*)buf) == 0); 360 | 361 | memset(buf, 0, 0xff); 362 | 363 | // Exactly correct size 364 | TEST_CHECK(zm_to_hex_header(&hdr, buf, HEX_HDR_STR_LEN) == HEX_HDR_STR_LEN); 365 | TEST_CHECK(strcmp("B01020304050a0b\r\x8a", (const char*)buf) == 0); 366 | 367 | memset(buf, 0, 0xff); 368 | 369 | // More than enough space 370 | TEST_CHECK(zm_to_hex_header(&hdr, buf, 0xff) == HEX_HDR_STR_LEN); 371 | TEST_CHECK(strcmp("B01020304050a0b\r\x8a", (const char*)buf) == 0); 372 | } 373 | 374 | void test_read_escaped() { 375 | // simple non-control characters 376 | set_buf("ABC", 3); 377 | 378 | TEST_CHECK(zm_read_escaped() == 'A'); 379 | TEST_CHECK(zm_read_escaped() == 'B'); 380 | TEST_CHECK(zm_read_escaped() == 'C'); 381 | 382 | // CLOSED if end of stream 383 | TEST_CHECK(zm_read_escaped() == CLOSED); 384 | 385 | // XON/XOFF are skipped 386 | set_buf("\x11\x11\x13Z", 4); 387 | 388 | TEST_CHECK(zm_read_escaped() == 'Z'); 389 | TEST_CHECK(zm_read_escaped() == CLOSED); 390 | 391 | // 5x CAN cancels 392 | set_buf("\x18\x18\x18\x18\x18ZYX", 8); 393 | TEST_CHECK(zm_read_escaped() == CANCELLED); 394 | TEST_CHECK(zm_read_escaped() == 'Z'); 395 | } 396 | 397 | TEST_LIST = { 398 | { "recv_buffer", test_recv_buffer }, 399 | { "IS_ERROR", test_is_error }, 400 | { "GET_ERROR_CODE", test_get_error_code }, 401 | { "ZVALUE", test_zvalue }, 402 | { "NONCONTROL", test_noncontrol }, 403 | { "IS_FIN", test_is_fin }, 404 | { "hex_to_nybble", test_hex_to_nybble }, 405 | { "hex_to_byte", test_hex_to_byte }, 406 | { "nybble_to_hex", test_nybble_to_hex }, 407 | { "byte_to_hex", test_byte_to_hex }, 408 | { "read_hex_header", test_read_hex_header }, 409 | { "calc_hdr_crc", test_calc_hdr_crc }, 410 | { "to_hex_header", test_to_hex_header }, 411 | { "test_read_escaped", test_read_escaped }, 412 | { NULL, NULL } 413 | }; 414 | -------------------------------------------------------------------------------- /zheaders.c: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Routines for working with Zmodem headers 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifdef ZDEBUG 17 | #include 18 | #endif 19 | 20 | #include "zheaders.h" 21 | #include "znumbers.h" 22 | #include "crc16.h" 23 | #include "crc32.h" 24 | 25 | void zm_calc_hdr_crc(ZHDR *hdr) { 26 | uint16_t crc = ucrc16(hdr->type, CRC_START_XMODEM); 27 | crc = ucrc16(hdr->flags.f3, crc); 28 | crc = ucrc16(hdr->flags.f2, crc); 29 | crc = ucrc16(hdr->flags.f1, crc); 30 | crc = ucrc16(hdr->flags.f0, crc); 31 | 32 | hdr->crc1 = CRC_MSB(crc); 33 | hdr->crc2 = CRC_LSB(crc); 34 | } 35 | 36 | uint16_t zm_calc_data_crc(uint8_t *buf, uint16_t len) { 37 | uint16_t crc = CRC_START_XMODEM; 38 | 39 | for (int i = 0; i < len; i++) { 40 | crc = ucrc16(buf[i], crc); 41 | } 42 | 43 | return crc; 44 | } 45 | 46 | uint32_t zm_calc_data_crc32(uint8_t *buf, uint16_t len) { 47 | return crc32((char*)buf, len); 48 | } 49 | 50 | ZRESULT zm_to_hex_header(ZHDR *hdr, uint8_t *buf, int max_len) { 51 | if (max_len < HEX_HDR_STR_LEN) { 52 | return OUT_OF_SPACE; 53 | } else { 54 | *buf++ = 'B'; // 01 55 | 56 | zm_byte_to_hex(hdr->type, buf); // 03 57 | buf += 2; 58 | zm_byte_to_hex(hdr->flags.f3, buf);// 05 59 | buf += 2; 60 | zm_byte_to_hex(hdr->flags.f2, buf);// 07 61 | buf += 2; 62 | zm_byte_to_hex(hdr->flags.f1, buf);// 09 63 | buf += 2; 64 | zm_byte_to_hex(hdr->flags.f0, buf);// 0b 65 | buf += 2; 66 | zm_byte_to_hex(hdr->crc1, buf); // 0d 67 | buf += 2; 68 | zm_byte_to_hex(hdr->crc2, buf); // 0f 69 | buf += 2; 70 | *buf++ = CR; // 10 71 | *buf++ = LF | 0x80; // 11 72 | 73 | return HEX_HDR_STR_LEN; 74 | } 75 | } 76 | 77 | ZRESULT zm_check_header_crc16(ZHDR *hdr, uint16_t crc) { 78 | if (CRC(hdr->crc1, hdr->crc2) == crc) { 79 | return OK; 80 | } else { 81 | return BAD_CRC; 82 | } 83 | } 84 | 85 | ZRESULT zm_check_header_crc32(ZHDR *hdr, uint32_t crc) { 86 | if (CRC32(hdr->crc1, hdr->crc2, hdr->crc3, hdr->crc4) == crc) { 87 | return OK; 88 | } else { 89 | return BAD_CRC; 90 | } 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /znumbers.c: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Numeric-related routines for Zmodem implementation. 13 | * ------------------------------------------------------------ 14 | */ 15 | #ifdef ZDEBUG 16 | #include 17 | #endif 18 | 19 | #include 20 | #include "ztypes.h" 21 | #include "znumbers.h" 22 | 23 | ZRESULT zm_hex_to_nybble(char c1) { 24 | switch (c1) { 25 | case '0': 26 | return 0x00; 27 | case '1': 28 | return 0x01; 29 | case '2': 30 | return 0x02; 31 | case '3': 32 | return 0x03; 33 | case '4': 34 | return 0x04; 35 | case '5': 36 | return 0x05; 37 | case '6': 38 | return 0x06; 39 | case '7': 40 | return 0x07; 41 | case '8': 42 | return 0x08; 43 | case '9': 44 | return 0x09; 45 | case 'a': 46 | return 0x0a; 47 | case 'b': 48 | return 0x0b; 49 | case 'c': 50 | return 0x0c; 51 | case 'd': 52 | return 0x0d; 53 | case 'e': 54 | return 0x0e; 55 | case 'f': 56 | return 0x0f; 57 | default: 58 | return BAD_DIGIT; 59 | } 60 | } 61 | 62 | ZRESULT zm_nybble_to_hex(uint8_t nybble) { 63 | switch (nybble) { 64 | case 0x0: 65 | return '0'; 66 | case 0x1: 67 | return '1'; 68 | case 0x2: 69 | return '2'; 70 | case 0x3: 71 | return '3'; 72 | case 0x4: 73 | return '4'; 74 | case 0x5: 75 | return '5'; 76 | case 0x6: 77 | return '6'; 78 | case 0x7: 79 | return '7'; 80 | case 0x8: 81 | return '8'; 82 | case 0x9: 83 | return '9'; 84 | case 0xa: 85 | return 'a'; 86 | case 0xb: 87 | return 'b'; 88 | case 0xc: 89 | return 'c'; 90 | case 0xd: 91 | return 'd'; 92 | case 0xe: 93 | return 'e'; 94 | case 0xf: 95 | return 'f'; 96 | default: 97 | return OUT_OF_RANGE; 98 | } 99 | } 100 | 101 | ZRESULT zm_byte_to_hex(uint8_t byte, uint8_t *buf) { 102 | uint16_t h1 = zm_nybble_to_hex(BMSN(byte)); 103 | 104 | if (IS_ERROR(h1)) { 105 | return h1; 106 | } else { 107 | uint16_t h2 = zm_nybble_to_hex(BLSN(byte)); 108 | 109 | if (IS_ERROR(h2)) { 110 | return h2; 111 | } else { 112 | *buf++ = h1; 113 | *buf = h2; 114 | 115 | return OK; 116 | } 117 | } 118 | } 119 | 120 | ZRESULT zm_hex_to_byte(unsigned char c1, unsigned char c2) { 121 | uint16_t n1,n2; 122 | 123 | n1 = zm_hex_to_nybble(c1); 124 | n2 = zm_hex_to_nybble(c2); 125 | 126 | if (n1 == BAD_DIGIT || n2 == BAD_DIGIT) { 127 | DEBUGF("Got bad digit: [0x%02x, 0x%02x]\n", c1, c2); 128 | return BAD_DIGIT; 129 | } else { 130 | return NTOB(n1,n2); 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /zserial.c: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Generic serial routines for Zmodem 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifdef ZDEBUG 17 | #include 18 | #endif 19 | 20 | #ifndef ZEMBEDDED 21 | #include 22 | #else 23 | #include "embedded.h" 24 | #endif 25 | 26 | #include "zserial.h" 27 | #include "zheaders.h" 28 | #include "znumbers.h" 29 | 30 | #include "crc16.h" 31 | #include "crc32.h" 32 | 33 | static uint8_t in_32bit_block = 0; 34 | 35 | ZRESULT zm_read_crlf() { 36 | uint16_t c = zm_read_escaped();//zm_recv(); 37 | 38 | if (IS_ERROR(c)) { 39 | DEBUGF("CRLF: Got error on first character: 0x%04x\n", c); 40 | return c; 41 | } else if (c == LF || c == (LF | 0x80)) { 42 | TRACEF("CRLF: Got LF on first character: OK\n"); 43 | return OK; 44 | } else if (c == CR || c == (CR | 0x80)) { 45 | TRACEF("CRLF: Got CR on first character, await LF\n"); 46 | c = zm_read_escaped(); //zm_recv(); 47 | 48 | if (IS_ERROR(c)) { 49 | return c; 50 | DEBUGF("CRLF: Got error on second character: 0x%04x\n", c); 51 | } else if (c == LF || c == (LF | 0x80)) { 52 | TRACEF("CRLF: Got LF on second character: OK\n"); 53 | return OK; 54 | } else { 55 | TRACEF("CRLF: Got corruption on second character: 0x%04x\n", c); 56 | return CORRUPTED; 57 | } 58 | } else { 59 | DEBUGF("CRLF: Got corruption on first character: 0x%04x\n", c); 60 | return CORRUPTED; 61 | } 62 | } 63 | 64 | ZRESULT zm_read_hex_byte() { 65 | int c1 = zm_recv(), c2; 66 | 67 | if (IS_ERROR(c1)) { 68 | return c1; 69 | } else { 70 | c2 = zm_recv(); 71 | if (IS_ERROR(c2)) { 72 | return c2; 73 | } else { 74 | return zm_hex_to_byte(c1, c2); 75 | } 76 | } 77 | } 78 | 79 | ZRESULT zm_read_escaped() { 80 | ZRESULT c; 81 | 82 | while (true) { 83 | c = zm_recv(); 84 | 85 | // Return immediately if non-control character or error 86 | if (NONCONTROL(c) || IS_ERROR(c)) { 87 | if (IS_ERROR(c)) { 88 | TRACEF(" >> READ_ESCAPED: IS ERROR: [0x%04x]\n", c); 89 | return c; 90 | } else { 91 | TRACEF(" >> READ_ESCAPED: Normal : [0x%02x]\n", ZVALUE(c)); 92 | return c; 93 | } 94 | } 95 | 96 | switch (c) { 97 | case XON: 98 | case XON | 0x80: 99 | case XOFF: 100 | case XOFF | 0x80: 101 | TRACEF(" >> READ_ESCAPED: Skipped XON/XOFF\n"); 102 | continue; 103 | case ZDLE: 104 | TRACEF(" >> READ_ESCAPED: Got ZDLE\n"); 105 | goto gotzdle; 106 | default: 107 | TRACEF(" >> READ_ESCAPED: Control : 0x%02x [%c]\n", c, ZVALUE(c)); 108 | return c; 109 | } 110 | } 111 | 112 | gotzdle: 113 | 114 | /* Picked up this trick from the original ZM implementation. Took me a few 115 | * minutes to figure it out, so here's what's going on. 116 | * 117 | * At the begining, c is **definitely** ZDLE (a.k.a. CAN, important to note that 118 | * they're the same!) so we can say we've already received exactly one CAN before 119 | * we enter this section. 120 | * 121 | * (We know this because the condition for entering this section was that we 122 | * received that ZDLE (a.k.a. CAN), and anything other than ZDLE was just 123 | * returned, give or take some swallowing of software flow control characters). 124 | * 125 | * 1. The first 'if' reads the next character, and immediately returns on error. 126 | * If no error, c is set to the next character. 127 | * 128 | * 2. We check the character read in 1. If it **was** CAN (making two CANs so far): 129 | * * Read the next character into c, immediately return on error. 130 | * 131 | * If it **wasn't** CAN, this step does nothing (i.e. nothing is read), and 132 | * (crucially) the rest of these steps will be skipped too, since c never changes. 133 | * **This part is important**. 134 | * 135 | * 3. We check the character read in 2. If it was CAN (making three CANs so far): 136 | * * Read the next character into c, immediately return on error. 137 | * 138 | * This is the clever part - if the character in read in 1 **was not** CAN, 139 | * then no character will have been read in step 2. So we're checking the 140 | * character read in 1 again, and it still cannot be CAN so again we won't read 141 | * another character. 142 | * 143 | * Furthermore, if the character read in 1 **was** CAN, but the character read 144 | * in step 2 **was not** CAN, then we won't see CAN in this step, or in any of 145 | * the following steps, and they'll all be skipped. 146 | * 147 | * 4. We check the character read in 3. If it was CAN (making four CANs so far): 148 | * * Read the next character into c, immediately return on error. 149 | * 150 | * Again, if the character we read in any of the preceeding steps **was not** CAN, 151 | * then c still hasn't changed because we haven't read any more characters. 152 | * 153 | * 5. Now, we enter the switch. At this point, we know the following to be true: 154 | * 155 | * * If c is CAN, we have **definitely** seen five CANs, indicating 156 | * the other end wants to cancel the transfer, **or** 157 | * 158 | * * c is definitely **not** CAN, but WAS preceeded by exactly one ZDLE, 159 | * so is either protocol control, or an escaped character. 160 | * 161 | */ 162 | if (IS_ERROR(c = zm_recv())) 163 | return c; 164 | if (c == CAN && IS_ERROR(c = zm_recv())) 165 | return c; 166 | if (c == CAN && IS_ERROR(c = zm_recv())) 167 | return c; 168 | if (c == CAN && IS_ERROR(c = zm_recv())) 169 | return c; 170 | 171 | switch (c) { 172 | case CAN: 173 | DEBUGF(" >> READ_ESCAPED: Got five CANs\n"); 174 | return CANCELLED; 175 | case ZCRCE: 176 | DEBUGF(" >> READ_ESCAPED: Got ZCRCE\n"); 177 | return GOT_CRCE; 178 | case ZCRCG: 179 | DEBUGF(" >> READ_ESCAPED: Got ZCRCG\n"); 180 | return GOT_CRCG; 181 | case ZCRCQ: 182 | DEBUGF(" >> READ_ESCAPED: Got ZCRCQ\n"); 183 | return GOT_CRCQ; 184 | case ZCRCW: 185 | DEBUGF(" >> READ_ESCAPED: Got ZCRCW\n"); 186 | return GOT_CRCW; 187 | case ZRUB0: 188 | DEBUGF(" >> READ_ESCAPED: Got ZRUB0\n"); 189 | return 0x7f; 190 | case ZRUB1: 191 | DEBUGF(" >> READ_ESCAPED: Got ZRUB1\n"); 192 | return 0xff; 193 | default: 194 | if ((c & 0x60) == 0x40) { 195 | TRACEF(" >> READ_ESCAPED: Got escaped character: 0x%02x\n", (c ^ 0x40)); 196 | return c ^ 0x40; 197 | } 198 | } 199 | 200 | DEBUGF(" >> READ_ESCAPED: Got bad control character 0x%02x", ZVALUE(c)); 201 | return BAD_ESCAPE; 202 | } 203 | 204 | /* Just read a data block - no CRC checking is done; see read_data_block */ 205 | static ZRESULT recv_data_block(uint8_t *buf, uint16_t *len) { 206 | uint16_t max = *len; 207 | *len = 0; 208 | 209 | while (*len < max) { 210 | ZRESULT c = zm_read_escaped(); 211 | 212 | if (IS_ERROR(c)) { 213 | DEBUGF(" >> RECV_BLOCK: GOT ERROR: 0x%04x\n", c); 214 | return c; 215 | } else { 216 | // Always add, even if frameend, as CRC takes that into account... 217 | buf[(*len)++] = ZVALUE(c); 218 | 219 | if (IS_FIN(c)) { 220 | return c; 221 | } 222 | } 223 | } 224 | 225 | return OUT_OF_SPACE; 226 | } 227 | 228 | ZRESULT zm_read_data_block(uint8_t *buf, uint16_t *len) { 229 | DEBUGF(" >> READ_BLOCK: Reading %d-bit block\n", in_32bit_block ? 32 : 16); 230 | ZRESULT result = recv_data_block(buf, len); 231 | DEBUGF(" >> READ_BLOCK: Result of data block recv is [0x%04x] (got %d character(s))\n", result, *len); 232 | 233 | if (IS_ERROR(result)) { 234 | return result; 235 | } else { 236 | // CRC bytes are ZDLE escaped! 237 | ZRESULT crc1 = zm_read_escaped(); 238 | if (IS_ERROR(crc1)) { 239 | DEBUGF(" >> READ_BLOCK: Error while reading crc1: 0x%04x\n", crc1); 240 | return crc1; 241 | } 242 | 243 | ZRESULT crc2 = zm_read_escaped(); 244 | if (IS_ERROR(crc2)) { 245 | return crc2; 246 | DEBUGF(" >> READ_BLOCK: Error while reading crc2: 0x%04x\n", crc2); 247 | } 248 | 249 | if (in_32bit_block) { 250 | ZRESULT crc3 = zm_read_escaped(); 251 | if (IS_ERROR(crc3)) { 252 | return crc3; 253 | DEBUGF(" >> READ_BLOCK: Error while reading crc3: 0x%04x\n", crc3); 254 | } 255 | 256 | ZRESULT crc4 = zm_read_escaped(); 257 | if (IS_ERROR(crc4)) { 258 | return crc4; 259 | DEBUGF(" >> READ_BLOCK: Error while reading crc4: 0x%04x\n", crc4); 260 | } 261 | 262 | DEBUGF(" >> READ_BLOCK: Calculate CRC32 for block len: %d\n", *len); 263 | uint32_t recv_crc = CRC32(ZVALUE(crc1), ZVALUE(crc2), ZVALUE(crc3), ZVALUE(crc4)); 264 | uint32_t calc_crc = zm_calc_data_crc32(buf, *len); 265 | 266 | if (recv_crc == calc_crc) { 267 | DEBUGF(" >> READ_BLOCK: CRC32 is good (recv: 0x%08x; calc: 0x%08x)\n", recv_crc, calc_crc); 268 | return result; 269 | } else { 270 | DEBUGF(" >> READ_BLOCK: CRC32 is borked (recv: 0x%08x; calc: 0x%08x)\n", recv_crc, calc_crc); 271 | return BAD_CRC; 272 | } 273 | } else { 274 | DEBUGF(" >> READ_BLOCK: Calculate CRC16 for block len: %d\n", *len); 275 | uint16_t recv_crc = CRC(ZVALUE(crc1), ZVALUE(crc2)); 276 | uint16_t calc_crc = zm_calc_data_crc(buf, *len); 277 | 278 | if (recv_crc == calc_crc) { 279 | DEBUGF(" >> READ_BLOCK: CRC is good (recv: 0x%04x; calc: 0x%04x)\n", recv_crc, calc_crc); 280 | return result; 281 | } else { 282 | DEBUGF(" >> READ_BLOCK: CRC is borked (recv: 0x%04x; calc: 0x%04x)\n", recv_crc, calc_crc); 283 | return BAD_CRC; 284 | } 285 | } 286 | } 287 | } 288 | 289 | 290 | ZRESULT zm_await(char *str, char *buf, int buf_size) { 291 | memset(buf, 0, 4); 292 | int ptr = 0; 293 | 294 | while(true) { 295 | ZRESULT c = zm_read_escaped(); 296 | 297 | if (IS_ERROR(c)) { 298 | return c; 299 | } else { 300 | // shift buffer if necessary 301 | if (ptr == buf_size - 1) { 302 | for (int i = 0; i < buf_size - 2; i++) { 303 | buf[i] = buf[i+1]; 304 | } 305 | buf[buf_size - 2] = ZVALUE(c); 306 | } else { 307 | buf[ptr++] = ZVALUE(c); 308 | } 309 | 310 | #ifdef ZTRACE 311 | TRACEF("Buf is ["); 312 | for (int i = 0; i < buf_size; i++) { 313 | TRACEF("%02x ", (buf[i] & 0xFF)); 314 | } 315 | TRACEF("]\n"); 316 | #endif 317 | 318 | // TODO if we don't use strcmp, we don't need buf to be one char longer... 319 | if (strcmp(str, buf) == 0) { 320 | DEBUGF("AWAIT: Target received; Await completed...\n"); 321 | return OK; 322 | } 323 | } 324 | } 325 | } 326 | 327 | ZRESULT zm_await_zdle() { 328 | while (true) { 329 | int c = zm_recv(); 330 | 331 | if (IS_ERROR(c)) { 332 | DEBUGF("Got error :(\n"); 333 | return c; 334 | } else { 335 | switch (c) { 336 | case ZPAD: 337 | case ZPAD | 0200: 338 | TRACEF("Got ZPAD...\n"); 339 | continue; 340 | case ZDLE: 341 | TRACEF("Got ZDLE\n"); 342 | return OK; 343 | case XON: 344 | case XOFF: 345 | TRACEF("Got XON/XOFF\n"); 346 | continue; 347 | default: 348 | #ifdef ZDEBUG 349 | DEBUGF("Got unknown (0x%02x)", c); 350 | if (NONCONTROL(c)) { 351 | DEBUGF(" [%c]\n", c); 352 | } else { 353 | DEBUGF(" [CTL]\n"); 354 | } 355 | #endif 356 | continue; 357 | } 358 | } 359 | } 360 | } 361 | 362 | ZRESULT zm_read_hex_header(ZHDR *hdr) { 363 | uint8_t *ptr = (uint8_t*)hdr; 364 | memset(hdr, 0xc0, sizeof(ZHDR)); 365 | uint16_t crc = CRC_START_XMODEM; 366 | 367 | // Set flag that next block will be CRC16 368 | in_32bit_block = 0; 369 | 370 | // TODO maybe don't treat header as a stream of bytes, which would remove 371 | // the need to have the all-byte layout in ZHDR struct... 372 | for (int i = 0; i < ZHDR_SIZE - 2; i++) { 373 | // TODO use read_hex_byte here... 374 | uint16_t c1 = zm_recv(); 375 | 376 | if (IS_ERROR(c1)) { 377 | DEBUGF("READ_HEX: Character %d/1 is error: 0x%04x\n", i, c1); 378 | return c1; 379 | } else if (IS_ZVALUE(c1,ZEOF)) { 380 | DEBUGF("READ_HEX: Character %d/1 is EOF\n", i); 381 | return CLOSED; 382 | } else { 383 | TRACEF("READ_HEX: Character %d/1 is good: 0x%04x\n", i, c1); 384 | uint16_t c2 = zm_recv(); 385 | 386 | if (IS_ERROR(c2)) { 387 | DEBUGF("READ_HEX: Character %d/2 is error: 0x%04x\n", i, c2); 388 | return c2; 389 | } else if (IS_ZVALUE(c2,ZEOF)) { 390 | DEBUGF("READ_HEX: Character %d/2 is EOF\n", i); 391 | return CLOSED; 392 | } else { 393 | TRACEF("READ_HEX: Character %d/2 is good: 0x%04x\n", i, c2); 394 | uint16_t b = zm_hex_to_byte(c1,c2); 395 | 396 | if (IS_ERROR(b)) { 397 | DEBUGF("READ_HEX: hex_to_byte %d is error: 0x%04x\n", i, b); 398 | return b; 399 | } else { 400 | TRACEF("READ_HEX: Byte %d is good: 0x%02x\n", i, b); 401 | TRACEF("Byte %d; hdr at 0x%0llx; ptr at 0x%0llx\n", i, (uint64_t)hdr, (uint64_t)ptr); 402 | *ptr++ = (uint8_t)b; 403 | 404 | if (i < ZHDR_SIZE - 4) { 405 | TRACEF("Will update CRC for byte %d\n", i); 406 | crc = ucrc16(b, crc); 407 | } else { 408 | TRACEF("Won't update CRC for byte %d\n", i); 409 | } 410 | 411 | TRACEF("READ_HEX: CRC after byte %d is 0x%04x\n", i, crc); 412 | } 413 | } 414 | } 415 | } 416 | 417 | DEBUGF("Received header; Dump is:\n"); 418 | DEBUG_DUMPHDR_R(hdr); 419 | 420 | DEBUGF("READ_HEX: All read; check CRC (Received: 0x%04x; Computed: 0x%04x)\n", CRC(hdr->crc1, hdr->crc2), crc); 421 | return zm_check_header_crc16(hdr, crc); 422 | } 423 | 424 | ZRESULT zm_read_binary16_header(ZHDR *hdr) { 425 | uint8_t *ptr = (uint8_t*)hdr; 426 | memset(hdr, 0xc0, sizeof(ZHDR)); 427 | uint16_t crc = CRC_START_XMODEM; 428 | 429 | // Set flag that next block will be CRC16 430 | in_32bit_block = 0; 431 | 432 | for (int i = 0; i < ZHDR_SIZE - 2; i++) { 433 | uint16_t b = zm_read_escaped(); 434 | 435 | if (IS_ERROR(b)) { 436 | DEBUGF("READ_BIN16: Character %d/1 is error: 0x%04x\n", i, b); 437 | return b; 438 | } else { 439 | DEBUGF("READ_BIN16: Byte %d is good: 0x%02x\n", i, b); 440 | TRACEF("Byte %d; hdr at 0x%0llx; ptr at 0x%0llx\n", i, (uint64_t)hdr, (uint64_t)ptr); 441 | *ptr++ = (uint8_t)b; 442 | 443 | if (i < ZHDR_SIZE - 4) { 444 | TRACEF("Will update CRC for byte %d\n", i); 445 | crc = ucrc16(b, crc); 446 | } else { 447 | TRACEF("Won't update CRC for byte %d\n", i); 448 | } 449 | 450 | DEBUGF("READ_BIN16: CRC after byte %d is 0x%04x\n", i, crc); 451 | } 452 | } 453 | 454 | DEBUG_DUMPHDR(hdr); 455 | 456 | DEBUGF("READ_BIN16: All read; check CRC (Received: 0x%04x; Computed: 0x%04x)\n", CRC(hdr->crc1, hdr->crc2), crc); 457 | return zm_check_header_crc16(hdr, crc); 458 | } 459 | 460 | ZRESULT zm_read_binary32_header(ZHDR *hdr) { 461 | uint8_t *ptr = (uint8_t*)hdr; 462 | memset(hdr, 0xc0, sizeof(ZHDR)); 463 | uint32_t crc = CRC_START_32; 464 | 465 | // Set flag that next block will be CRC32 466 | in_32bit_block = 1; 467 | 468 | for (int i = 0; i < ZHDR_SIZE; i++) { 469 | uint16_t b = zm_read_escaped(); 470 | 471 | if (IS_ERROR(b)) { 472 | DEBUGF("READ_BIN32: Character %d/1 is error: 0x%04x\n", i, b); 473 | return b; 474 | } else { 475 | TRACEF("READ_BIN32: Byte %d is good: 0x%02x\n", i, b); 476 | TRACEF("Byte %d; hdr at 0x%0llx; ptr at 0x%0llx\n", i, (uint64_t)hdr, (uint64_t)ptr); 477 | *ptr++ = (uint8_t)b; 478 | 479 | if (i < ZHDR_SIZE - 4) { 480 | TRACEF("Will update CRC for byte %d\n", i); 481 | crc = ucrc32(b, crc); 482 | } else { 483 | TRACEF("Won't update CRC for byte %d\n", i); 484 | } 485 | 486 | DEBUGF("READ_BIN32: CRC after byte %d is 0x%04x\n", i, crc); 487 | } 488 | } 489 | 490 | DEBUG_DUMPHDR(hdr); 491 | 492 | // Negate, as it seems libcrc is generating JAMCRC with update_crc_32... 493 | crc = ~crc; 494 | 495 | DEBUGF("READ_BIN32: All read; check CRC (Received: 0x%08x; Computed: 0x%08x)\n", 496 | CRC32(hdr->crc1, hdr->crc2, hdr->crc3, hdr->crc4), crc); 497 | 498 | return zm_check_header_crc32(hdr, crc); 499 | } 500 | 501 | ZRESULT zm_await_header(ZHDR *hdr) { 502 | uint16_t result; 503 | 504 | while (true) { 505 | if (zm_await_zdle() == OK) { 506 | DEBUGF("Got ZDLE, awaiting type...\n"); 507 | ZRESULT frame_type = zm_read_escaped(); 508 | 509 | if (IS_ERROR(frame_type)) { 510 | DEBUGF("Got error reading frame type: 0x%04x\n", frame_type); 511 | continue; 512 | } 513 | 514 | switch (ZVALUE(frame_type)) { 515 | case ZHEX: 516 | DEBUGF("Reading HEX header\n"); 517 | result = zm_read_hex_header(hdr); 518 | 519 | if (result == OK) { 520 | DEBUGF("Got valid header\n"); 521 | return zm_read_crlf(); 522 | } else { 523 | DEBUGF("Didn't get valid header [0x%02x]\n", result); 524 | return result; 525 | } 526 | case ZBIN16: 527 | DEBUGF("Reading BIN16 header\n"); 528 | result = zm_read_binary16_header(hdr); 529 | 530 | if (result == OK) { 531 | DEBUGF("Got valid header\n"); 532 | return OK; 533 | } else { 534 | DEBUGF("Didn't get valid header [0x%02x]\n", result); 535 | return result; 536 | } 537 | case ZBIN32: 538 | DEBUGF("Reading BIN32 header\n"); 539 | result = zm_read_binary32_header(hdr); 540 | 541 | if (result == OK) { 542 | DEBUGF("Got valid header\n"); 543 | return OK; 544 | } else { 545 | DEBUGF("Didn't get valid header [0x%02x]\n", result); 546 | return result; 547 | } 548 | default: 549 | DEBUGF("Got bad frame type '%c' [%02x]\n", ZVALUE(frame_type), ZVALUE(frame_type)); 550 | return BAD_FRAME_TYPE; 551 | } 552 | } else { 553 | return CLOSED; 554 | } 555 | } 556 | } 557 | 558 | ZRESULT zm_send_sz(uint8_t *data) { 559 | register uint16_t result; 560 | 561 | while (*data) { 562 | result = zm_send(*data++); 563 | 564 | if (IS_ERROR(result)) { 565 | return result; 566 | } 567 | } 568 | 569 | return OK; 570 | } 571 | 572 | static ZRESULT just_send_hex_hdr(uint8_t *buf) { 573 | zm_send(ZPAD); 574 | zm_send(ZPAD); 575 | zm_send(ZDLE); 576 | 577 | DEBUGF(" >> SEND (raw): [%s]\n", buf); 578 | 579 | ZRESULT result = zm_send_sz(buf); 580 | if (IS_ERROR(result)) { 581 | return result; 582 | } else { 583 | return zm_send(XON); 584 | } 585 | } 586 | 587 | ZRESULT zm_send_hex_hdr(ZHDR *hdr) { 588 | static uint8_t buf[HEX_HDR_STR_LEN]; 589 | 590 | zm_calc_hdr_crc(hdr); 591 | ZRESULT result = zm_to_hex_header(hdr, buf, HEX_HDR_STR_LEN); 592 | 593 | if (IS_ERROR(result)) { 594 | return result; 595 | } else { 596 | return just_send_hex_hdr(buf); 597 | } 598 | 599 | } 600 | 601 | ZRESULT zm_send_pos_hdr(uint8_t type, uint32_t pos) { 602 | static ZHDR hdr; 603 | 604 | #ifdef ZDEBUG 605 | static const ZHDR* hdrptr = &hdr; 606 | #endif 607 | 608 | hdr.type = type; 609 | #ifdef ZM_BIG_ENDIAN 610 | hdr.position.p3 = (uint8_t)(pos & 0xff); 611 | hdr.position.p2 = (uint8_t)(pos >> 8) & 0xff; 612 | hdr.position.p1 = (uint8_t)(pos >> 16) & 0xff; 613 | hdr.position.p0 = (uint8_t)(pos >> 24) & 0xff; 614 | #else 615 | hdr.position.p0 = (uint8_t)(pos & 0xff); 616 | hdr.position.p1 = (uint8_t)(pos >> 8) & 0xff; 617 | hdr.position.p2 = (uint8_t)(pos >> 16) & 0xff; 618 | hdr.position.p3 = (uint8_t)(pos >> 24) & 0xff; 619 | #endif 620 | 621 | DEBUGF("Sending position header as hex; Dump is:\n"); 622 | DEBUG_DUMPHDR_P(hdrptr); 623 | 624 | return zm_send_hex_hdr(&hdr); 625 | } 626 | 627 | ZRESULT zm_send_flags_hdr(uint8_t type, uint8_t f0, uint8_t f1, uint8_t f2, uint8_t f3) { 628 | static ZHDR hdr; 629 | 630 | #ifdef ZDEBUG 631 | static const ZHDR* hdrptr = &hdr; 632 | #endif 633 | 634 | hdr.type = type; 635 | hdr.flags.f0 = f0; 636 | hdr.flags.f1 = f1; 637 | hdr.flags.f2 = f2; 638 | hdr.flags.f3 = f3; 639 | 640 | DEBUGF("Sending flags header as hex; Dump is:\n"); 641 | DEBUG_DUMPHDR_F(hdrptr); 642 | 643 | return zm_send_hex_hdr(&hdr); 644 | } 645 | 646 | 647 | --------------------------------------------------------------------------------