/* * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * For more information, please refer to */ #include "gen.h" #include #include /* * POCSAG Protocol Constants * * POCSAG uses FSK modulation at 512, 1200, or 2400 baud. * * Frame structure: * Preamble (576+ bits alternating 1/0) * Sync codeword (32 bits) * Batch of 16 codewords (8 frames x 2 codewords each) * [Repeat sync + batch as needed] * * Codeword structure (32 bits): * Bit 31: Message flag (0=address, 1=message) * Bits 30-11: Data (20 bits) * Bits 10-1: BCH parity (10 bits) * Bit 0: Even parity */ #define POCSAG_SYNC 0x7CD215D8u #define POCSAG_IDLE 0x7A89C197u #define POCSAG_PREAMBLE_BITS 576 /*---------------------------------------------------------------------------*/ #include "bch.h" /* Inject bit errors into a 31-bit codeword (excludes parity bit) */ static uint32_t inject_errors(uint32_t codeword, int num_errors, unsigned int *seed) { int positions[3] = {-1, -1, -1}; int i, j, pos; for (i = 0; i < num_errors && i < 3; i++) { /* Simple PRNG for reproducible errors */ do { *seed = *seed * 1103515245 + 12345; pos = (*seed >> 16) % 31 + 1; /* Bits 1-31, avoid parity bit 0 */ /* Ensure unique positions */ for (j = 0; j < i; j++) { if (positions[j] == pos) { pos = -1; break; } } } while (pos < 0); positions[i] = pos; codeword ^= (1u << pos); } return codeword; } /* Build address codeword * Address format: * Bit 31: 0 (address indicator) * Bits 30-13: Address bits 20-3 (18 bits) * Bits 12-11: Function code (2 bits) * Bits 10-1: BCH parity * Bit 0: Even parity * * Note: Address bits 2-0 are encoded in the frame position (0-7) */ static uint32_t build_address_codeword(uint32_t address, int function) { /* Data field: 18 bits of address (bits 20-3) + 2 bits function */ uint32_t data = ((address >> 3) << 2) | (function & 3); return bch_pocsag_encode(data); } /* Build message codeword for numeric data * Message format: * Bit 31: 1 (message indicator) * Bits 30-11: 5 BCD digits (20 bits, 4 bits each) * Bits 10-1: BCH parity * Bit 0: Even parity */ static uint32_t build_message_codeword(uint32_t data20) { /* Set message flag (bit 20 of data field) */ uint32_t data = (1u << 20) | (data20 & 0xFFFFF); return bch_pocsag_encode(data); } /* Numeric character to BCD conversion (inverse of pocsag.c conv_table) */ static int char_to_bcd(char c) { switch (c) { case '0': return 0; case '1': return 8; case '2': return 4; case '3': return 12; case '4': return 2; case '5': return 10; case '6': return 6; case '7': return 14; case '8': return 1; case '9': return 9; case 'U': case 'u': return 13; case ' ': return 3; case '-': return 11; case '.': return 5; case '[': return 15; case ']': return 7; default: return 3; /* Space for unknown */ } } /* Reverse bits in a 7-bit value (for alphanumeric encoding) */ static unsigned char rev7(unsigned char b) { return ((b << 6) & 64) | ((b >> 6) & 1) | ((b << 4) & 32) | ((b >> 4) & 2) | ((b << 2) & 16) | ((b >> 2) & 4) | (b & 8); } /* Pack a 7-bit character into buffer at bit position n*7 (MSB first) */ static void put7(unsigned char *buf, int n, unsigned char val) { int start_bit = n * 7; int b; for (b = 0; b < 7; b++) { int bit_pos = start_bit + b; int byte_idx = bit_pos / 8; int bit_in_byte = 7 - (bit_pos % 8); /* MSB = bit 7 */ if (val & (0x40 >> b)) /* MSB of val is bit 6 */ buf[byte_idx] |= (1 << bit_in_byte); } } /* Encode message into codewords array, returns number of codewords used */ static int encode_message(const char *msg, int function, uint32_t *codewords, int max_codewords) { int num_codewords = 0; int len = strlen(msg); if (function == 0) { /* Numeric encoding: 5 BCD digits per codeword */ int i = 0; while (i < len && num_codewords < max_codewords) { uint32_t data = 0; int digits_packed = 0; for (int j = 0; j < 5 && i < len; j++, i++) { data = (data << 4) | char_to_bcd(msg[i]); digits_packed++; } /* Pad with spaces if less than 5 digits */ while (digits_packed < 5) { data = (data << 4) | 3; /* Space */ digits_packed++; } codewords[num_codewords++] = build_message_codeword(data); } } else { /* Alphanumeric encoding: pack 7-bit characters */ unsigned char buffer[256] = {0}; int bit_count = 0; for (int i = 0; i < len; i++) { unsigned char c = rev7(msg[i] & 0x7F); put7(buffer, i, c); bit_count += 7; } /* Convert buffer to message codewords (20 bits each) */ int nibble_idx = 0; int total_nibbles = (bit_count + 3) / 4; /* Round up to nibbles */ while (nibble_idx < total_nibbles && num_codewords < max_codewords) { uint32_t data = 0; /* Pack 5 nibbles (20 bits) into each codeword */ int nibbles_in_cw = 0; for (int j = 0; j < 5 && nibble_idx < total_nibbles; j++, nibble_idx++) { int byte_pos = nibble_idx / 2; int nibble; if ((nibble_idx % 2) == 0) { nibble = (buffer[byte_pos] >> 4) & 0xF; } else { nibble = buffer[byte_pos] & 0xF; } data = (data << 4) | nibble; nibbles_in_cw++; } /* Pad remaining nibbles with zeros */ while (nibbles_in_cw < 5) { data = (data << 4); nibbles_in_cw++; } codewords[num_codewords++] = build_message_codeword(data); } } return num_codewords; } /*---------------------------------------------------------------------------*/ /* Initialize POCSAG generator */ void gen_init_pocsag(struct gen_params *p, struct gen_state *s) { int i; uint32_t codewords[256]; int num_msg_codewords; int frame_position; int batch_count; unsigned int error_seed = 12345; /* Seed for error injection */ /* Determine frame position from address (low 3 bits) */ frame_position = p->p.pocsag.address & 7; /* Encode the message */ num_msg_codewords = encode_message(p->p.pocsag.message, p->p.pocsag.function, codewords, 256); /* Calculate number of batches needed * Each batch has 16 codewords (8 frames x 2 codewords) * Address goes in frame_position, message follows * We need at least 1 idle codeword after message for decoder to detect end */ int slots_needed = 1 + num_msg_codewords + 1; /* Address + message + 1 idle for end detection */ int slots_in_first_batch = 16 - (frame_position * 2); if (slots_needed <= slots_in_first_batch) { batch_count = 1; } else { batch_count = 1 + ((slots_needed - slots_in_first_batch + 15) / 16); } /* Calculate total bits: * - Preamble: 576 bits * - Per batch: 32 (sync) + 16*32 (codewords) = 544 bits */ int total_bits = POCSAG_PREAMBLE_BITS + batch_count * (32 + 16 * 32); /* Allocate data buffer */ s->s.pocsag.datalen = (total_bits + 7) / 8; if (s->s.pocsag.datalen > sizeof(s->s.pocsag.data)) { fprintf(stderr, "gen_pocsag: message too long\n"); s->s.pocsag.datalen = sizeof(s->s.pocsag.data); } memset(s->s.pocsag.data, 0, sizeof(s->s.pocsag.data)); /* Build the transmission */ int bit_idx = 0; /* Preamble: alternating 1010... pattern (starts with 1) */ for (i = 0; i < POCSAG_PREAMBLE_BITS; i++) { if ((i & 1) == 0) s->s.pocsag.data[bit_idx / 8] |= (0x80 >> (bit_idx % 8)); bit_idx++; } /* Build batches */ int msg_cw_idx = 0; int address_sent = 0; for (int batch = 0; batch < batch_count; batch++) { /* Sync codeword (32 bits, MSB first) - also inject errors if requested */ uint32_t sync_word = POCSAG_SYNC; if (p->p.pocsag.errors > 0) sync_word = inject_errors(sync_word, p->p.pocsag.errors, &error_seed); for (i = 31; i >= 0; i--) { if (sync_word & (1u << i)) s->s.pocsag.data[bit_idx / 8] |= (0x80 >> (bit_idx % 8)); bit_idx++; } /* 16 codewords (8 frames x 2 codewords) */ for (int frame = 0; frame < 8; frame++) { for (int cw = 0; cw < 2; cw++) { uint32_t codeword; if (!address_sent && frame == frame_position && cw == 0) { /* Send address codeword */ codeword = build_address_codeword(p->p.pocsag.address, p->p.pocsag.function); if (p->p.pocsag.errors > 0) codeword = inject_errors(codeword, p->p.pocsag.errors, &error_seed); address_sent = 1; } else if (address_sent && msg_cw_idx < num_msg_codewords) { /* Send message codeword */ codeword = codewords[msg_cw_idx++]; if (p->p.pocsag.errors > 0) codeword = inject_errors(codeword, p->p.pocsag.errors, &error_seed); } else { /* Send idle codeword */ codeword = POCSAG_IDLE; if (p->p.pocsag.errors > 0) codeword = inject_errors(codeword, p->p.pocsag.errors, &error_seed); } /* Output codeword (32 bits, MSB first) */ for (i = 31; i >= 0; i--) { if (codeword & (1u << i)) s->s.pocsag.data[bit_idx / 8] |= (0x80 >> (bit_idx % 8)); bit_idx++; } } } } s->s.pocsag.bit_idx = 0; s->s.pocsag.datalen = (bit_idx + 7) / 8; s->s.pocsag.baud = p->p.pocsag.baud; s->s.pocsag.bitph = 0; } /* Generate POCSAG samples */ int gen_pocsag(signed short *buf, int buflen, struct gen_params *p, struct gen_state *s) { int num = 0; /* Samples per bit based on baud rate */ /* At 22050 Hz sample rate: * 512 baud: ~43.07 samples/bit * 1200 baud: ~18.375 samples/bit * 2400 baud: ~9.1875 samples/bit */ float samples_per_bit = 22050.0f / s->s.pocsag.baud; while (num < buflen) { if ((unsigned int)s->s.pocsag.bit_idx >= s->s.pocsag.datalen * 8) return num; /* Done */ /* Get current bit */ int byte_idx = s->s.pocsag.bit_idx / 8; int bit_pos = 7 - (s->s.pocsag.bit_idx % 8); int bit = (s->s.pocsag.data[byte_idx] >> bit_pos) & 1; /* Output sample: negative for 1, positive for 0 (matches decoder's !bit inversion) */ /* If inverted, flip the polarity */ if (p->p.pocsag.invert) buf[num++] = bit ? p->ampl : -p->ampl; else buf[num++] = bit ? -p->ampl : p->ampl; /* Advance bit phase */ s->s.pocsag.bitph += 1.0f; if (s->s.pocsag.bitph >= samples_per_bit) { s->s.pocsag.bitph -= samples_per_bit; s->s.pocsag.bit_idx++; } } return num; }