diff --git a/build/Makefile b/build/Makefile index 181544a..4f0f725 100644 --- a/build/Makefile +++ b/build/Makefile @@ -24,22 +24,28 @@ endif ifeq ($(findstring CYGWIN,$(shell uname)),CYGWIN) endif -EXEC=broadcast_tv broadcast_fm rf_jammer +EXEC=broadcast_tv broadcast_fm rf_jammer pocsag all: $(EXEC) -broadcast_fm: broadcast_fm.o rds.o rds_stream_dump.o wave.o modulator.o FIR_Audio_Filter_Filter.o FM_Baseband_Filter.o AudioPreemphasis_Filter.o FIR_RDS_Passband_Filter.o hxcmod.o rand_gen.o +broadcast_fm: broadcast_fm.o rds.o rds_stream_dump.o wave.o modulator.o FIR_Audio_Filter_Filter.o FM_Baseband_Filter.o AudioPreemphasis_Filter.o FIR_RDS_Passband_Filter.o hxcmod.o rand_gen.o std_crc32.o $(CC) -o $@ $^ $(LDFLAGS) -broadcast_tv: broadcast_tv.o composite.o wave.o modulator.o bmp_file.o +broadcast_tv: broadcast_tv.o composite.o wave.o modulator.o bmp_file.o std_crc32.o $(CC) -o $@ $^ $(LDFLAGS) -rf_jammer: rf_jammer.o wave.o modulator.o rand_gen.o composite.o +rf_jammer: rf_jammer.o wave.o modulator.o rand_gen.o composite.o std_crc32.o + $(CC) -o $@ $^ $(LDFLAGS) + +pocsag: pocsag.o wave.o modulator.o serial.o std_crc32.o utils.o $(CC) -o $@ $^ $(LDFLAGS) broadcast_fm.o: ../src/broadcast_fm/broadcast_fm.c $(CC) -o $@ -c $< $(CFLAGS) +pocsag.o: ../src/pocsag/pocsag.c + $(CC) -o $@ -c $< $(CFLAGS) + rf_jammer.o: ../src/rf_jammer/rf_jammer.c $(CC) -o $@ -c $< $(CFLAGS) @@ -52,9 +58,15 @@ rds_stream_dump.o: ../src/broadcast_fm/rds_stream_dump.c utils.o: ../src/common/utils.c $(CC) -o $@ -c $< $(CFLAGS) +serial.o: ../src/common/serial.c + $(CC) -o $@ -c $< $(CFLAGS) + wave.o: ../src/common/wave.c $(CC) -o $@ -c $< $(CFLAGS) +std_crc32.o: ../src/common/std_crc32.c + $(CC) -o $@ -c $< $(CFLAGS) + modulator.o: ../src/common/modulator.c $(CC) -o $@ -c $< $(CFLAGS) diff --git a/src/pocsag/pocsag.c b/src/pocsag/pocsag.c new file mode 100644 index 0000000..1bb1e04 --- /dev/null +++ b/src/pocsag/pocsag.c @@ -0,0 +1,733 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------H----H--X----X-----CCCCC----22222----0000-----0000------11----------// +//----------H----H----X-X-----C--------------2---0----0---0----0--1--1-----------// +//---------HHHHHH-----X------C----------22222---0----0---0----0-----1------------// +//--------H----H----X--X----C----------2-------0----0---0----0-----1-------------// +//-------H----H---X-----X---CCCCC-----222222----0000-----0000----1111------------// +//-------------------------------------------------------------------------------// +//----------------------------------------------------- http://hxc2001.free.fr --// +/////////////////////////////////////////////////////////////////////////////////// +// File : pocsag.c +// Contains: a pocsag transmitter +// +// This file is part of rf-tools. +// +// Written by: Jean-François DEL NERO +// +// Copyright (C) 2022-2026 Jean-François DEL NERO +// +// You are free to do what you want with this code. +// A credit is always appreciated if you use it into your product :) +// +// Change History (most recent first): +/////////////////////////////////////////////////////////////////////////////////// + +// +// Disclaimer / Legal warning : Radio spectrum and the law +// +// In most countries the use of any radio transmitting device is required to be +// either licensed or specifically exempted from licensing under the local regulator. +// Other than as used in accordance with a licence (or exemption), +// the use of radio equipment is illegal. +// +// So take care to limit the emitting range and power when testing this software ! +// +// -------------------------------------------------------------------------------- +// Example from the FCC (United states) : +// +// Part 15 Devices +// +// Unlicensed operation on the AM and FM radio broadcast bands is permitted for +// some extremely low powered devices covered under Part 15 of the FCC's rules. +// On FM frequencies, these devices are limited to an effective service range +// of approximately 200 feet (61 meters). +// See 47 CFR (Code of Federal Regulations) Section 15.239, and the July 24, +// 1991 Public Notice (still in effect). +// +// On the AM broadcast band, these devices are limited to an effective service +// range of approximately 200 feet (61 meters). See 47 CFR Sections 15.207, +// 15.209, 15.219, and 15.221. These devices must accept any interference +// caused by any other operation, which may further limit the effective service +// range. +// +// For more information on Part 15 devices, please see OET Bulletin No. 63 +// ("Understanding the FCC Regulations for Low-Power, Non-Licensed Transmitters"). +// Questions not answered by this Bulletin can be directed to the FCC's Office +// of Engineering and Technology, Customer Service Branch, at the Columbia, +// Maryland office, phone (301) 362 - 3000. +// +// [...] +// +// Penalties for Operation Without A Permit or License +// +// The Commission considers unauthorized broadcast operation to be a serious matter. +// Presently, the maximum penalty for operating an unlicensed or "pirate" broadcast +// station (one which is not permitted under Part 15 or is not a Carrier Current +// Station or Campus Radio Station) is set at $10,000 for a single violation or a +// single day of operation, up to a total maximum amount of $75,000. +// +// Adjustments may be made upwards or downwards depending on the circumstances +// involved. Equipment used for an unauthorized operation may also be confiscated. +// There are also criminal penalties (fine and/or imprisonment) for +// "willfully and knowingly" operating a radio station without a license. +// DON'T DO IT! +// +// More at : https://www.fcc.gov/media/radio/low-power-radio-general-information +// +// -------------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wave.h" +#include "modulator.h" +#include "serial.h" +#include "utils.h" + +#define IQ_SAMPLE_RATE 2000000 +#define CENTRAL_IF_FREQ 0 + +#define BUFFER_IQ_SAMPLES_SIZE (1024*8) + +#define printf(fmt...) do { \ + if(!stdoutmode) \ + fprintf(stdout, fmt); \ + } while (0) + +int stdoutmode; + +#pragma pack(1) + +typedef struct _poc_batch +{ + uint32_t sync; + uint32_t words[8*2]; +} poc_batch; + +#pragma pack() + +typedef struct _poc +{ + int preambule_cnt; + int frm_cnt; + int frm_end; +}poc; + +int isOption(int argc, char* argv[],char * paramtosearch,char * argtoparam) +{ + int param=1; + int i,j; + + char option[512]; + + memset(option,0,sizeof(option)); + + while(param<=argc) + { + if(argv[param]) + { + if(argv[param][0]=='-') + { + memset(option,0,sizeof(option)); + + j=0; + i=1; + while( argv[param][i] && argv[param][i]!=':' && ( j < (sizeof(option) - 1)) ) + { + option[j]=argv[param][i]; + i++; + j++; + } + + if( !strcmp(option,paramtosearch) ) + { + if(argtoparam) + { + argtoparam[0] = 0; + + if(argv[param][i]==':') + { + i++; + j=0; + while( argv[param][i] && j < (512 - 1) ) + { + argtoparam[j]=argv[param][i]; + i++; + j++; + } + argtoparam[j]=0; + return 1; + } + else + { + return -1; + } + } + else + { + return 1; + } + } + } + } + param++; + } + + return 0; +} + +void printhelp(char* argv[]) +{ + printf("Options:\n"); + printf(" -stdout \t\t\t: IQ stream send to stdout\n"); + printf(" -generate \t\t\t: Generate the IQ stream\n"); + printf(" -baud:[BAUD]\t\t\t: Baud rate setting (Default:1200)\n"); + printf(" -ric:[RIC]\t\t\t: RIC address (Default:8)\n"); + printf(" -func:[function_code]\t\t: Function code (Default:0 Min/Max:0-3)\n"); + printf(" -freqshift:[Hz]\t\t: FSK frequency shift (Default:4500 -> +4500Hz / -4500Hz)\n"); + printf(" -smprate:[Hz]\t\t\t: IQ Sample rate (Default:2000000\n"); + printf(" -alpha\t\t\t: Alphanumeric mode (Default: Numeric mode\n"); + + printf(" -help \t\t\t: This help\n\n"); + printf("Example : ./pocsag -generate -stdout -ric:154232 -message:\"Hello\" -alpha | hackrf_transfer -f 466200500 -t - -x 10 -a 0 -s 2000000\n"); + printf("\n"); +} + +uint32_t adr_codeword(uint32_t ric_address, uint8_t func) +{ + uint32_t val; + + // Get the 18 bits most significant bits ric address part. + // The 3 lower bits is the frame position + + val = ( ( (ric_address>>3) & ((0x1<<18)-1) ) << 13 ) | + ( ( (uint32_t)(func&3)) << 11); + + return val; +} + +// BCH(31,21) (21 bit data - 31 bit code word, without the parity) +// g(x) = x^10 + x^9 + x^8 + x^6 + x^5 + x^3 + 1 + +uint32_t calc_pocsag_bch(uint32_t codeword) +{ + uint32_t bit = 0; + uint32_t parity = 0; + uint32_t tmp_codeword; + uint32_t shiftr; + + tmp_codeword = codeword & 0xFFFFF800; + shiftr = tmp_codeword; + + // BCH bits + for (bit = 1; bit <= 21; bit++) // 21 bits of data + { + if (shiftr & 0x80000000) + { + shiftr ^= (0x769U << (32U - (31-21) - 1U)); + } + shiftr <<= 1; + } + + tmp_codeword |= (shiftr >> 21); + + // Parity bit + shiftr = tmp_codeword; + bit = 32; + while ((shiftr != 0) && (bit > 0)) + { + if (shiftr & 1) + { + parity++; + } + shiftr >>= 1; + bit--; + } + + return tmp_codeword | (parity&1); +} + +unsigned char pocsag_nextbyte(poc * ctx, poc_batch * batches, int batchcnt) +{ + if(ctx->preambule_cnt < (72 + 32) ) + { + // > 576 bits + ctx->preambule_cnt++; + return 0xAA; + } + + if(ctx->frm_cnt < ( sizeof(poc_batch) * batchcnt ) ) + { + uint8_t * ptr; + + ptr = (uint8_t *)batches; + + unsigned char val = *(ptr + ctx->frm_cnt); + + ctx->frm_cnt++; + + return val; + } + else + { + ctx->frm_end = 1; + return 0x00; + } + + + return 0x00; +} + +#define POCSAG_SYNC_CW 0x7CD215D8 +#define POCSAG_IDLE_CW 0x7A89C197 + +void init_batch(poc_batch * batch) +{ + batch->sync = BIGENDIAN_DWORD( calc_pocsag_bch(POCSAG_SYNC_CW) ); + for(int i=0;i<16;i++) + { + batch->words[i] = BIGENDIAN_DWORD( calc_pocsag_bch(POCSAG_IDLE_CW) ); + } +} + +int set_numerical_frame(poc_batch * batch, int batchcnt, unsigned int ric, unsigned int func, char * message) +{ + int ba,b,i,w,framecnt; + int digitcnt; + uint8_t charcode; + uint32_t word; + + i = 0; + while(message[i] != 0) + { + i++; + } + + digitcnt = i; + + framecnt = digitcnt / 5; + if( digitcnt % 5 ) + framecnt++; + + if( framecnt > batchcnt) + { + return -1; + } + + ba = 0; + w = (ric&7)*2; + + batch[ba].words[w++] = BIGENDIAN_DWORD( calc_pocsag_bch(adr_codeword(ric, func))); + + b = 0; + + digitcnt = 0; + + // 4 bits per digit - bits 30 <>11 -> 5 digits per code word + word = 0x80000000; + i = 0; + while(message[i] != 0) + { + switch(message[i]) + { + case 'U': + charcode = 0xB; + break; + case ' ': + charcode = 0xC; + break; + case '-': + charcode = 0xD; + break; + case ']': + charcode = 0xE; + break; + case '[': + charcode = 0xF; + break; + default: + if(message[i]>='0' && message[i] <= '9') + { + charcode = (message[i] - '0')&0xF; + } + else + { + charcode = 0xA; + } + break; + } + + word |= (((uint32_t)(LUT_QuartetBitsInverter[charcode]))<<(27-((b%5)*4)) ); + + b++; + if(!(b%5)) + { + batch[ba].words[w] = BIGENDIAN_DWORD( calc_pocsag_bch( word ) ); // Spaces + + w++; + + if( w >= 16) + { + w = 0; + ba++; + } + + word = 0x80000000; + } + i++; + } + + if((b%5)) + { + while((b%5)) + { + word |= ( ((uint32_t)LUT_QuartetBitsInverter[0xC])<<(27-((b%5)*4)) ); + b++; + } + + batch[ba].words[w] = BIGENDIAN_DWORD( calc_pocsag_bch( word ) ); + } + + if( ( batch[ba].words[15] != POCSAG_IDLE_CW ) && ( batch[ba].words[14] != POCSAG_IDLE_CW ) ) + { + return ba+1+1; + } + else + { + return ba+1; + } + + return ba+1; +} + +int set_alphanum_frame(poc_batch * batch, int batchcnt, unsigned int ric, unsigned int func, char * message) +{ + int ba,b,i,w,framecnt; + int digitcnt; + uint8_t charcode; + uint32_t word; + unsigned char stream[80+1]; + int bitin,bitout; + + i = 0; + while(message[i] != 0) + { + i++; + } + + digitcnt = i; + + framecnt = (digitcnt*7) / 20; + if( (digitcnt*7) % 20 ) + framecnt++; + + if( framecnt > batchcnt) + { + return -1; + } + + memset(&stream,0,sizeof(stream)); + i = 0; + bitin = 0; + bitout = 0; + while( message[bitin/7] && ((bitout>>3) < sizeof(stream)-1) ) + { + if( LUT_ByteBitsInverter[message[bitin/7]<<1] & ( 0x40 >> (bitin%7) ) ) + stream[bitout>>3] |= ( 0x80 >> (bitout&7) ); + + bitin++; + bitout++; + } + + stream[sizeof(stream)-1] = 0; + + digitcnt = bitout >> 2; + if(bitout&3) + digitcnt++; + + ba = 0; + w = (ric&7)*2; + + batch[ba].words[w++] = BIGENDIAN_DWORD( calc_pocsag_bch(adr_codeword(ric, func))); + + b = 0; + + // 4 bits per digit - bits 30 <>11 -> 5 digits per code word + word = 0x80000000; + i = 0; + while( i < digitcnt ) + { + charcode = (stream[i>>1] >> (4*((i&1)^1)) & 0xF); + if( charcode > 127 ) + charcode = ' '; + + word |= (((uint32_t)(charcode))<<(27-((b%5)*4)) ); + + b++; + if(!(b%5)) + { + batch[ba].words[w] = BIGENDIAN_DWORD( calc_pocsag_bch( word ) ); // Spaces + + w++; + + if( w >= 16) + { + w = 0; + ba++; + } + + word = 0x80000000; + } + i++; + } + + if((b%5)) + { + while((b%5)) + { + word |= ( ((uint32_t)0x0)<<(27-((b%5)*4)) ); + b++; + } + + batch[ba].words[w] = BIGENDIAN_DWORD( calc_pocsag_bch( word ) ); + } + + if( ( batch[ba].words[15] != POCSAG_IDLE_CW ) && ( batch[ba].words[14] != POCSAG_IDLE_CW ) ) + { + return ba+1+1; + } + else + { + return ba+1; + } + + return ba+1; +} + +#define MAXBATCHCNT 128 + +int main(int argc, char* argv[]) +{ + char temp_str[512]; + unsigned int i,j; + char message[512]; + int ric; + int func; + int baud; + int smprate; + int freqshift; + int batchescnt; + int alpha; + int batchbruteforce; + + wave_io * wave1; + + iq_wave_gen iqgen; + + poc pocctx; + poc_batch batches[MAXBATCHCNT]; + + uint16_t iq_wave_buf[BUFFER_IQ_SAMPLES_SIZE]; + + serial_gen ser; + + memset(&pocctx,0,sizeof(pocctx)); + + stdoutmode = 0; + if(isOption(argc,argv,"stdout",NULL)>0) + { + stdoutmode = 1; + } + + baud = 1200; + if(isOption(argc,argv,"baud",(char*)&temp_str)>0) + { + baud = atoi(temp_str); + } + + ric = 8; + if(isOption(argc,argv,"ric",(char*)&temp_str)>0) + { + ric = atoi(temp_str); + } + + func = 0; + if(isOption(argc,argv,"func",(char*)&temp_str)>0) + { + func = atoi(temp_str); + } + + freqshift = 4500; + if(isOption(argc,argv,"freqshift",(char*)&temp_str)>0) + { + freqshift = atoi(temp_str); + } + + message[0] = 0; + if(isOption(argc,argv,"message",(char*)&message)>0) + { + } + + alpha = 0; + if(isOption(argc,argv,"alpha",NULL)>0) + { + alpha = 1; + } + + smprate = IQ_SAMPLE_RATE; + if(isOption(argc,argv,"smprate",(char*)&temp_str)>0) + { + smprate = atoi(temp_str); + } + + batchbruteforce = 0; + if(isOption(argc,argv,"batch",NULL)>0) + { + batchbruteforce = 1; + } + + if(!stdoutmode) + { + printf("pocsag v0.0.1.1\n"); + printf("Copyright (C) 2026 Jean-Francois DEL NERO\n"); + printf("This program comes with ABSOLUTELY NO WARRANTY\n"); + printf("This is free software, and you are welcome to redistribute it\n"); + printf("under certain conditions;\n\n"); + } + + // help option... + if(isOption(argc,argv,"help",0)>0) + { + printhelp(argv); + } + + if(isOption(argc,argv,"generate",0)>0) + { + // IQ Modulator + iqgen.phase = 0; + iqgen.Frequency = CENTRAL_IF_FREQ; + iqgen.Amplitude = 120; + iqgen.sample_rate = smprate; + + if(stdoutmode) + { + // stdout / stream mode : IQ are outputed to the stdout -> use a pipe to hackrf_transfer + wave1 = create_wave(NULL,iqgen.sample_rate,WAVE_FILE_FORMAT_RAW_8BITS_IQ); + } + else + { + // file mode : create iq + wav files + wave1 = create_wave("out.sdriq",iqgen.sample_rate,WAVE_FILE_FORMAT_SDRIQ_8BITS_IQ);//WAVE_FILE_FORMAT_WAV_8BITS_STEREO); + } + + if(wave1) + { + serial_init(&ser, 8, smprate, baud); + + memset(&pocctx,0,sizeof(pocctx)); + + for(i=0;i