From 6944594bfabe9fd3eee7e53381048b491acf6dbb Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sat, 19 Dec 2015 22:03:01 +0100 Subject: m MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- src/fodtmf-send.c | 503 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/send.c | 503 ------------------------------------------------------ 2 files changed, 503 insertions(+), 503 deletions(-) create mode 100644 src/fodtmf-send.c delete mode 100644 src/send.c diff --git a/src/fodtmf-send.c b/src/fodtmf-send.c new file mode 100644 index 0000000..b841e1f --- /dev/null +++ b/src/fodtmf-send.c @@ -0,0 +1,503 @@ +/** + * Copyright © 2015 Mattias Andrée (maandree@member.fsf.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* -lm */ +#include /* dep: alsa-lib (pkg-config alsa) */ + + + +#define UTYPE uint32_t +#define SMIN INT32_MIN +#define SMAX INT32_MAX +#define FORMAT SND_PCM_FORMAT_U32 + +#define SAMPLE_RATE 52000 /* Hz */ +#define DURATION 100 /* ms */ /* 100 ms → 10 Bd → 5 B/s */ +#define LATENCY 100000 /* µs */ + + + +/** + * Number of samples per tone. + */ +#define N (SAMPLE_RATE / 1000 * DURATION) + + + +/** + * The process's name. + */ +static const char* argv0; + +/** + * Audio descriptor. + */ +static snd_pcm_t* sound_handle = NULL; + +/** + * Audio playback buffer for each nibble. + */ +static UTYPE buffers[16][N]; + +/** + * The number of parity tones in the hamming code. + */ +static int parity = 3; + +/** + * Whether to add an additional parity tone (of all + * data tones) in addition to those numerated by `parity`. + */ +static int use_extra_parity = 0; + +/** + * Whether to add redundancy frequencies in the tones. + */ +static int use_redundant_freq = 0; + +/** + * Frequencies multiple for the redundant frequencies. + */ +static double redundant_freq_mul = 0; + +/** + * Data buffer used for error correcting code support. + */ +static int* data; + +/** + * Code buffer used for error correcting code support. + */ +static int* code; + +/** + * The number elements that can be stored in `data`. + */ +static int data_n; + +/** + * The number elements that can be stored in `code`. + */ +static int code_n; + +/** + * File descriptor to write the audio to (with any metadata) + * rather than sending it to the audio subsystem. + * + * This is available for developers of the receiver. + * It is not intend to be used for anything else. + */ +static int output_fd = -1; + + + +/** + * Use to accept signals, without doing anything + * about them, just be interrupted. + * + * @param signo The received signal. + */ +static void signoop(int signo) +{ + (void) signo; +} + + +/** + * Get the tones used to transmit a nibble. + * + * @param nibble The nibble. + * @param low Output parameter for the lower frequency. + * @param high Output parameter for the higher frequency. + */ +static void get_freq(int nibble, int* low, int* high) +{ + static const int LOW[] = { 697, 770, 852, 941 }; + static const int HIGH[] = { 1209, 1336, 1477, 1663 }; + + *low = LOW[(nibble >> 0) & 0x03]; + *high = HIGH[(nibble >> 2) & 0x03]; +} + + +/** + * Initialise to before for each nibble. + */ +static void init_buffers(void) +{ +#define GENERATE_TONE(tone) sin(2 * M_PI * ((double)i / (SAMPLE_RATE / (tone)))) + + size_t i; + UTYPE* buffer; + int j, low, high; + + for (j = 0; j < 16; j++) + { + buffer = buffers[j]; + get_freq(j, &low, &high); + + if (use_redundant_freq) + for (i = 0; i < N; i++) + buffer[i] = (GENERATE_TONE(low) + + GENERATE_TONE(high) + + GENERATE_TONE(low * redundant_freq_mul) + + GENERATE_TONE(high * redundant_freq_mul)) * + (SMAX / 4) - SMIN; + else + for (i = 0; i < N; i++) + buffer[i] = (GENERATE_TONE(low) + GENERATE_TONE(high)) * + (SMAX / 2) - SMIN; + } +} + + +/** + * Transmit a nibble. + * + * @param n The nibble. + * @return 0 on success, -1 on error. + */ +static int send_nibble(int n) +{ + UTYPE* buffer = buffers[n]; + snd_pcm_sframes_t frames; + int r; + + if (output_fd >= 0) + { + char* buf = (char*)buffer; + size_t ptr = 0; + ssize_t wrote; + while (ptr < N) + { + wrote = write(output_fd, buf + ptr, N - ptr); + if (wrote < 0) + return -1; + ptr += (size_t)write; + } + return 0; + } + + r = frames = snd_pcm_writei(sound_handle, buffer, N); + if (frames < 0) + r = frames = snd_pcm_recover(sound_handle, r, 0 /* do not print error reason? */); + if (r < 0) + { + fprintf(stderr, "%s: snd_pcm_writei: %s\n", argv0, snd_strerror(r)); + errno = 0; + return -1; + } + if ((r > 0) && ((size_t)r < N)) + printf("%s: short write: expected %zu, wrote %zu\n", argv0, N, (size_t)frames); + + return 0; +} + + +/** + * Transmit a byte. + * + * @param c The byte. + * @return 0 on success, -1 on error. + */ +static int send_byte(int c) +{ +#ifdef DEBUG + fprintf(stderr, "%s: sending byte: %i\n", argv0, c); +#endif + if (send_nibble((c >> 0) & 0x0F)) return -1; + if (send_nibble((c >> 4) & 0x0F)) return -1; + return 0; +} + + +/** + * Transmit a byte with error correcting code. + * + * This function uses a buffer so that it can + * create an error correcting code. The bytes + * are not transmitted until this buffer has + * been filled. + * + * @param c The byte, the negative of that byte (intended + * only for `CHAR_END` and `CHAR_CANCEL`) to fill + * the remained of the buffer with the byte. + * Note that if a negative value is used, it is + * no necessary that anything will happen. + * @return 0 on success, -1 on error. + */ +static int send_byte_with_ecc(int c) +{ + static int ptr = 0; + int i, j, d, p; + + if (parity < 2) + { + if (c < 0) + return 0; + if (send_byte(c)) + return -1; + if (parity) + if (send_byte(c)) + return -1; + if (use_extra_parity) + if (send_byte(c)) + return -1; + return 0; + } + + /* Fill buffer. */ + if (c < 0) + { + if (ptr > 0) + while (ptr < data_n) + data[ptr++] = -c; + } + else + data[ptr++] = c; + + /* Is it full? */ + if (ptr < data_n) + return 0; + ptr = 0; + + /* Hamming code. */ + memset(code, 0, code_n * sizeof(*code)); + for (i = 1, j = 0; i <= (1 << parity) - 1; i++) + { + if ((i & -i) == i) + continue; + for (d = i, p = 0; d; d >>= 1, p++) + if (d & 1) + code[(1 << p) - 1] ^= data[j]; + code[i - 1] = data[j]; + j++; + } + if (use_extra_parity) + for (i = 0; i < data_n; i++) + code[(1 << parity) - 1] ^= data[i]; + + /* Transmit. */ + for (i = 0; i < code_n; i++) + if (send_byte(code[i])) + return -1; + + return 0; +} + + +/** + * Transmit a chunk of bytes. + * + * @param buf The chunk to transmit. + * @param n The number of bytes in the chunk. + * @return 0 on success, -1 on failure. + */ +static int send_chunk(char* buf, size_t n) +{ + size_t i; + int c; + + for (i = 0; i < n; i++) + { + c = buf[i]; + if ((c == CHAR_ESCAPE) || (c == CHAR_CANCEL) || (c == CHAR_END)) + if (send_byte_with_ecc(CHAR_ESCAPE)) + goto qfile; + if (send_byte_with_ecc(c)) + goto qfile; + } + + return 0; + qfile: + errno = 0; + fail: + return -1; +} + + +/** + * Read all input from stdin and transmit it as it is being read. + * + * @return 0 on success, -1 on failure. + */ +static int send_file(void) +{ + char buf[1024]; + ssize_t n; + + for (;;) + { + n = read(STDIN_FILENO, buf, sizeof(buf)); + if (n < 0) goto fail; + if (n == 0) break; + if (send_chunk(buf, (size_t)n)) + goto fail; + } + + return 0; + fail: + return -1; +} + + +/** + * Read all input from stdin, and then transmit it. + * + * @return 0 on success, -1 on failure. + */ +static int send_term(void) +{ + char* buf = NULL; + size_t size = 0; + size_t ptr = 0; + ssize_t n; + void* new; + int saved_errno; + + for (;;) + { + if (ptr == size) + { + size = size ? (size << 1) : 128; + new = realloc(buf, size); + if (new == NULL) + goto fail; + buf = new; + } + n = read(STDIN_FILENO, buf + ptr, size - ptr); + if (n < 0) goto fail; + if (n == 0) break; + ptr += (size_t)n; + } + if (send_chunk(buf, ptr)) + goto fail; + + return 0; + fail: + saved_errno = errno; + free(buf); + errno = saved_errno; + return -1; +} + + +/** + * Transmit a file over audio. + * + * @param argc The number of elements in `argv`. + * @param argv Command line arguments. + * @return 0 on success, 1 on failure. + */ +int main(int argc, char* argv[]) +{ + struct sigaction act; + int r, rc = 1; + + /* Parse command line. */ + argv0 = argc ? argv[0] : ""; + for (;;) + { + r = getopt (argc, argv, "f:pr:o:"); + if (r == -1) + break; + else if (r == 'f') use_redundant_freq = 1, redundant_freq_mul = atof(optarg); + else if (r == 'p') use_extra_parity = 1; + else if (r == 'r') parity = atoi(optarg); + else if (r == 'o') output_fd = atoi(optarg); + else if (r != '?') + abort(); + } + + /* Set up signal handling. */ + siginterrupt(SIGTERM, 1); + siginterrupt(SIGQUIT, 1); + siginterrupt(SIGINT, 1); + siginterrupt(SIGHUP, 1); + sigemptyset(&(act.sa_mask)); + act.sa_handler = signoop; + act.sa_flags = 0; + sigaction(SIGTERM, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigprocmask(SIG_SETMASK, &(act.sa_mask), NULL); + + /* Generate the tones to play. */ + init_buffers(); + /* Generate buffers for error correcting code. */ + data_n = (1 << parity) - parity - 1; + code_n = (1 << parity) - 1 + use_extra_parity; + data = alloca(data_n * sizeof(*data)); + code = alloca(code_n * sizeof(*code)); + + /* Set up audio. */ + if (output_fd >= 0) + goto no_audio; + r = snd_pcm_open(&sound_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (r < 0) + return fprintf(stderr, "%s: snd_pcm_open: %s\n", *argv, snd_strerror(r)), 1; + if (sound_handle == NULL) + perror("snd_pcm_open"); + /* Configure audio. */ + r = snd_pcm_set_params(sound_handle, FORMAT, SND_PCM_ACCESS_RW_INTERLEAVED, 1 /* channels */, + SAMPLE_RATE, 1 /* allow resampling? */, LATENCY); + if (r < 0) + return fprintf(stderr, "%s: snd_pcm_set_params: %s\n", *argv, snd_strerror(r)), 1; + no_audio: + + /* Send message. */ + if (isatty(STDIN_FILENO)) + { + if (send_term()) + goto fail; + } + else + if (send_file()) + goto fail; + + /* Mark end of transmission. */ + if (send_byte_with_ecc(CHAR_END)) goto cleanup; + if (send_byte_with_ecc(-CHAR_END)) goto cleanup; + + /* Done! */ + rc = 0; + goto cleanup; + fail: + if (errno) + perror(argv0); + cleanup: + if (output_fd >= 0) + snd_pcm_close(sound_handle); + /* Mark aborted transmission. */ + if (rc) + { + send_byte_with_ecc(CHAR_CANCEL); + send_byte_with_ecc(-CHAR_CANCEL); + } + return rc; +} + diff --git a/src/send.c b/src/send.c deleted file mode 100644 index b841e1f..0000000 --- a/src/send.c +++ /dev/null @@ -1,503 +0,0 @@ -/** - * Copyright © 2015 Mattias Andrée (maandree@member.fsf.org) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "common.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include /* -lm */ -#include /* dep: alsa-lib (pkg-config alsa) */ - - - -#define UTYPE uint32_t -#define SMIN INT32_MIN -#define SMAX INT32_MAX -#define FORMAT SND_PCM_FORMAT_U32 - -#define SAMPLE_RATE 52000 /* Hz */ -#define DURATION 100 /* ms */ /* 100 ms → 10 Bd → 5 B/s */ -#define LATENCY 100000 /* µs */ - - - -/** - * Number of samples per tone. - */ -#define N (SAMPLE_RATE / 1000 * DURATION) - - - -/** - * The process's name. - */ -static const char* argv0; - -/** - * Audio descriptor. - */ -static snd_pcm_t* sound_handle = NULL; - -/** - * Audio playback buffer for each nibble. - */ -static UTYPE buffers[16][N]; - -/** - * The number of parity tones in the hamming code. - */ -static int parity = 3; - -/** - * Whether to add an additional parity tone (of all - * data tones) in addition to those numerated by `parity`. - */ -static int use_extra_parity = 0; - -/** - * Whether to add redundancy frequencies in the tones. - */ -static int use_redundant_freq = 0; - -/** - * Frequencies multiple for the redundant frequencies. - */ -static double redundant_freq_mul = 0; - -/** - * Data buffer used for error correcting code support. - */ -static int* data; - -/** - * Code buffer used for error correcting code support. - */ -static int* code; - -/** - * The number elements that can be stored in `data`. - */ -static int data_n; - -/** - * The number elements that can be stored in `code`. - */ -static int code_n; - -/** - * File descriptor to write the audio to (with any metadata) - * rather than sending it to the audio subsystem. - * - * This is available for developers of the receiver. - * It is not intend to be used for anything else. - */ -static int output_fd = -1; - - - -/** - * Use to accept signals, without doing anything - * about them, just be interrupted. - * - * @param signo The received signal. - */ -static void signoop(int signo) -{ - (void) signo; -} - - -/** - * Get the tones used to transmit a nibble. - * - * @param nibble The nibble. - * @param low Output parameter for the lower frequency. - * @param high Output parameter for the higher frequency. - */ -static void get_freq(int nibble, int* low, int* high) -{ - static const int LOW[] = { 697, 770, 852, 941 }; - static const int HIGH[] = { 1209, 1336, 1477, 1663 }; - - *low = LOW[(nibble >> 0) & 0x03]; - *high = HIGH[(nibble >> 2) & 0x03]; -} - - -/** - * Initialise to before for each nibble. - */ -static void init_buffers(void) -{ -#define GENERATE_TONE(tone) sin(2 * M_PI * ((double)i / (SAMPLE_RATE / (tone)))) - - size_t i; - UTYPE* buffer; - int j, low, high; - - for (j = 0; j < 16; j++) - { - buffer = buffers[j]; - get_freq(j, &low, &high); - - if (use_redundant_freq) - for (i = 0; i < N; i++) - buffer[i] = (GENERATE_TONE(low) + - GENERATE_TONE(high) + - GENERATE_TONE(low * redundant_freq_mul) + - GENERATE_TONE(high * redundant_freq_mul)) * - (SMAX / 4) - SMIN; - else - for (i = 0; i < N; i++) - buffer[i] = (GENERATE_TONE(low) + GENERATE_TONE(high)) * - (SMAX / 2) - SMIN; - } -} - - -/** - * Transmit a nibble. - * - * @param n The nibble. - * @return 0 on success, -1 on error. - */ -static int send_nibble(int n) -{ - UTYPE* buffer = buffers[n]; - snd_pcm_sframes_t frames; - int r; - - if (output_fd >= 0) - { - char* buf = (char*)buffer; - size_t ptr = 0; - ssize_t wrote; - while (ptr < N) - { - wrote = write(output_fd, buf + ptr, N - ptr); - if (wrote < 0) - return -1; - ptr += (size_t)write; - } - return 0; - } - - r = frames = snd_pcm_writei(sound_handle, buffer, N); - if (frames < 0) - r = frames = snd_pcm_recover(sound_handle, r, 0 /* do not print error reason? */); - if (r < 0) - { - fprintf(stderr, "%s: snd_pcm_writei: %s\n", argv0, snd_strerror(r)); - errno = 0; - return -1; - } - if ((r > 0) && ((size_t)r < N)) - printf("%s: short write: expected %zu, wrote %zu\n", argv0, N, (size_t)frames); - - return 0; -} - - -/** - * Transmit a byte. - * - * @param c The byte. - * @return 0 on success, -1 on error. - */ -static int send_byte(int c) -{ -#ifdef DEBUG - fprintf(stderr, "%s: sending byte: %i\n", argv0, c); -#endif - if (send_nibble((c >> 0) & 0x0F)) return -1; - if (send_nibble((c >> 4) & 0x0F)) return -1; - return 0; -} - - -/** - * Transmit a byte with error correcting code. - * - * This function uses a buffer so that it can - * create an error correcting code. The bytes - * are not transmitted until this buffer has - * been filled. - * - * @param c The byte, the negative of that byte (intended - * only for `CHAR_END` and `CHAR_CANCEL`) to fill - * the remained of the buffer with the byte. - * Note that if a negative value is used, it is - * no necessary that anything will happen. - * @return 0 on success, -1 on error. - */ -static int send_byte_with_ecc(int c) -{ - static int ptr = 0; - int i, j, d, p; - - if (parity < 2) - { - if (c < 0) - return 0; - if (send_byte(c)) - return -1; - if (parity) - if (send_byte(c)) - return -1; - if (use_extra_parity) - if (send_byte(c)) - return -1; - return 0; - } - - /* Fill buffer. */ - if (c < 0) - { - if (ptr > 0) - while (ptr < data_n) - data[ptr++] = -c; - } - else - data[ptr++] = c; - - /* Is it full? */ - if (ptr < data_n) - return 0; - ptr = 0; - - /* Hamming code. */ - memset(code, 0, code_n * sizeof(*code)); - for (i = 1, j = 0; i <= (1 << parity) - 1; i++) - { - if ((i & -i) == i) - continue; - for (d = i, p = 0; d; d >>= 1, p++) - if (d & 1) - code[(1 << p) - 1] ^= data[j]; - code[i - 1] = data[j]; - j++; - } - if (use_extra_parity) - for (i = 0; i < data_n; i++) - code[(1 << parity) - 1] ^= data[i]; - - /* Transmit. */ - for (i = 0; i < code_n; i++) - if (send_byte(code[i])) - return -1; - - return 0; -} - - -/** - * Transmit a chunk of bytes. - * - * @param buf The chunk to transmit. - * @param n The number of bytes in the chunk. - * @return 0 on success, -1 on failure. - */ -static int send_chunk(char* buf, size_t n) -{ - size_t i; - int c; - - for (i = 0; i < n; i++) - { - c = buf[i]; - if ((c == CHAR_ESCAPE) || (c == CHAR_CANCEL) || (c == CHAR_END)) - if (send_byte_with_ecc(CHAR_ESCAPE)) - goto qfile; - if (send_byte_with_ecc(c)) - goto qfile; - } - - return 0; - qfile: - errno = 0; - fail: - return -1; -} - - -/** - * Read all input from stdin and transmit it as it is being read. - * - * @return 0 on success, -1 on failure. - */ -static int send_file(void) -{ - char buf[1024]; - ssize_t n; - - for (;;) - { - n = read(STDIN_FILENO, buf, sizeof(buf)); - if (n < 0) goto fail; - if (n == 0) break; - if (send_chunk(buf, (size_t)n)) - goto fail; - } - - return 0; - fail: - return -1; -} - - -/** - * Read all input from stdin, and then transmit it. - * - * @return 0 on success, -1 on failure. - */ -static int send_term(void) -{ - char* buf = NULL; - size_t size = 0; - size_t ptr = 0; - ssize_t n; - void* new; - int saved_errno; - - for (;;) - { - if (ptr == size) - { - size = size ? (size << 1) : 128; - new = realloc(buf, size); - if (new == NULL) - goto fail; - buf = new; - } - n = read(STDIN_FILENO, buf + ptr, size - ptr); - if (n < 0) goto fail; - if (n == 0) break; - ptr += (size_t)n; - } - if (send_chunk(buf, ptr)) - goto fail; - - return 0; - fail: - saved_errno = errno; - free(buf); - errno = saved_errno; - return -1; -} - - -/** - * Transmit a file over audio. - * - * @param argc The number of elements in `argv`. - * @param argv Command line arguments. - * @return 0 on success, 1 on failure. - */ -int main(int argc, char* argv[]) -{ - struct sigaction act; - int r, rc = 1; - - /* Parse command line. */ - argv0 = argc ? argv[0] : ""; - for (;;) - { - r = getopt (argc, argv, "f:pr:o:"); - if (r == -1) - break; - else if (r == 'f') use_redundant_freq = 1, redundant_freq_mul = atof(optarg); - else if (r == 'p') use_extra_parity = 1; - else if (r == 'r') parity = atoi(optarg); - else if (r == 'o') output_fd = atoi(optarg); - else if (r != '?') - abort(); - } - - /* Set up signal handling. */ - siginterrupt(SIGTERM, 1); - siginterrupt(SIGQUIT, 1); - siginterrupt(SIGINT, 1); - siginterrupt(SIGHUP, 1); - sigemptyset(&(act.sa_mask)); - act.sa_handler = signoop; - act.sa_flags = 0; - sigaction(SIGTERM, &act, NULL); - sigaction(SIGQUIT, &act, NULL); - sigaction(SIGHUP, &act, NULL); - sigprocmask(SIG_SETMASK, &(act.sa_mask), NULL); - - /* Generate the tones to play. */ - init_buffers(); - /* Generate buffers for error correcting code. */ - data_n = (1 << parity) - parity - 1; - code_n = (1 << parity) - 1 + use_extra_parity; - data = alloca(data_n * sizeof(*data)); - code = alloca(code_n * sizeof(*code)); - - /* Set up audio. */ - if (output_fd >= 0) - goto no_audio; - r = snd_pcm_open(&sound_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); - if (r < 0) - return fprintf(stderr, "%s: snd_pcm_open: %s\n", *argv, snd_strerror(r)), 1; - if (sound_handle == NULL) - perror("snd_pcm_open"); - /* Configure audio. */ - r = snd_pcm_set_params(sound_handle, FORMAT, SND_PCM_ACCESS_RW_INTERLEAVED, 1 /* channels */, - SAMPLE_RATE, 1 /* allow resampling? */, LATENCY); - if (r < 0) - return fprintf(stderr, "%s: snd_pcm_set_params: %s\n", *argv, snd_strerror(r)), 1; - no_audio: - - /* Send message. */ - if (isatty(STDIN_FILENO)) - { - if (send_term()) - goto fail; - } - else - if (send_file()) - goto fail; - - /* Mark end of transmission. */ - if (send_byte_with_ecc(CHAR_END)) goto cleanup; - if (send_byte_with_ecc(-CHAR_END)) goto cleanup; - - /* Done! */ - rc = 0; - goto cleanup; - fail: - if (errno) - perror(argv0); - cleanup: - if (output_fd >= 0) - snd_pcm_close(sound_handle); - /* Mark aborted transmission. */ - if (rc) - { - send_byte_with_ecc(CHAR_CANCEL); - send_byte_with_ecc(-CHAR_CANCEL); - } - return rc; -} - -- cgit v1.2.3-70-g09d2