4
\$\begingroup\$

Running this up the flagpole to "put it out there".

The code has been compiled with an ancient C++ compiler for the niceties of declaring/defining variables as needed. With some minor tweaking, I'm fairly sure a modern C compiler would be content. (Please excuse the antiquated castings.)

What sets this apart from the pack (IMO) is that this Enigma "machine" configuration and message are loaded from a text file that can contain multiple separate configurations & messages. Instead of repeated modular divisions, rotors, the reflector and the plugboard are "in triplicate", automatically "wrapping 'round" as a circular rotor would do. From the configuration settings, "the device" is initialised; its rotors (with 'ringstellung'), its reflector and its plugboard. Then, the actual character substitution of each input character is an almost trivial cascade of table lookup operations.

This simulates both a 3 rotor and a 4 rotor (Kriegsmarine) Enigma. In the case of the former, the 4th rotor in this code operates as a simple pass-through operation (without substitution). Some may know that the Kriegsmarine's 4 rotor design allows the user to, with special settings, operate as a 3 rotor version.

Here's the code, followed by a sample text file containing a few configurations and messages.

Please note the expectation that the text file will be named "test0.txt" (or similar). The program will reproduce the file outputting to "test1.txt" with the enciphered string(s) appended. Running the program again nominating "test1.txt" as input, the file "test2.txt" will be created having "deciphered" what had been "enciphered" (using the same configuration settings.)

This has been written for my own amusement. Noteworthy is that there is very little error checking of the configuration strings. It's possible to use settings such as would require 2 or even 3 copies of the same rotor (not something that was practiced by Enigma users.)

One more thing: The "message" need not be uppercase alpha only. Whereas Enigma was to be used with groups of 5 characters, this version happily passes through any non-alpha characters. It's just a toy.

// Enigma
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys\stat.h>
typedef unsigned char byte;
/* GENERIC */
// Handle CR or CRLF, chopping block into individual lines
char *markEOL( char *cp ) {
 while( *cp && *cp != '\r' && *cp != '\n' ) cp++;
 if( *cp == '\r' ) *cp++ = '0円';
 *cp++ = '0円';
 return cp;
}
// Load file to alloc'd memory, preserving end-of-buffer; return pointer to buffer
char *loadFile( char *fname, char **pEOB ) {
 struct stat finfo;
 if( stat( fname, &finfo ) != 0 ) fprintf( stderr, "Cannot see '%s'\n", fname ), exit(1);
 FILE *fp;
 if( ( fp = fopen( fname, "rb" ) ) == NULL ) fprintf( stderr, "Cannot open '%s'\n", fname ), exit(1);
 char *buf;
 if( ( buf = (char*)malloc( finfo.st_size + 1) ) == NULL ) fprintf( stderr, "Malloc() failed\n" ), exit(1);
 if( fread( buf, 1, finfo.st_size, fp ) != (size_t)finfo.st_size ) fprintf( stderr, "Read incomplete\n" ), exit(1);
 fclose( fp );
 *pEOB = buf + finfo.st_size;
 **pEOB = '0円'; // set extra byte allocated to NULL (to use strXXX functions)
 return buf;
}
// Load a block, then segment it on boundaries (if any)
typedef struct SEGINF {
 char *begin, *end;
 struct SEGINF *nxt;
} seg_t;
seg_t *mkSgmt( char *from, char *to ) {
 seg_t *p = (seg_t*)malloc( sizeof *p );
 p->begin = from;
 p->end = to;
 p->nxt = NULL;
 return p;
}
seg_t *loadSegments( char *fName, char *pBndry ) {
 char *eob, *buf = loadFile( fName, &eob );
 seg_t *pRoot, *pTail;
 pRoot = pTail = mkSgmt( buf, eob );
 for( char *ep = buf; ( ep = strstr( ep, pBndry ) ) != NULL; ) {
 pTail->end = ep - 1;
 if( (ep = strchr( ep, '\n' ) ) != NULL )
 pTail = pTail->nxt = mkSgmt( ep + 1, eob );
 }
 return pRoot;
}
/* DEFINED */
char *rotors[] = { // 1 'Pseudo' + 8 standard rotors with 1or2 notches
 "ABCDEFGHIJKLMNOPQRSTUVWXYZ..##", // Pseudo that can be used as "straight thru"
 "EKMFLGDQVZNTOWYHXUSPAIBRCJ Q#", // I
 "AJDKSIRUXBLHWTMCQGZNPYFVOE E#", // II
 "BDFHJLCPRTXVZNYEIWGAKMUSQO V#", // III
 "ESOVPZJAYQUIRHXLNFTGKDCMWB J#", // IV
 "VZBRGITYUPSDNHLXAWMJQOFECK Z#", // V
 "JPGVOUMFYQBENHZRDKASXLICTW HU", // VI
 "NZJHGRCXMYSWBOUFAIVLPEKQDT HU", // VII
 "FKQHTLXOCBJSPDZRAMEWNIUYGV HU", // VIII
// Kreigsmarine used 2 'thin' rotors in combo with 2 'thin' reflectors
 "LEYJVCNIXWPBQMDRTAKZGFUHOS ##", // Beta
 "FSOKANUERHMBTIYCWLQPZXVGJD ##", // Gamma
};
char *reflB = "YRUHQSLDPXNGOKMIEBFZCWVJAT";
char *reflC = "FVPJIAOYEDRZXWGCTKUQSBNMHL";
char *reflBthin = "ENKQAUYWJICOPBLMDXZVFTHRGS"; // M4 only
char *reflCthin = "RDOBJNTKVEHMLFCWZAXGYIPSUQ"; // M4 only
// Configuration loaded from file
struct {
 bool verbose;
 char *cnfg;
 char refID;
 char sqnc[4];
 char ring[4];
 char init[4];
 char *swap;
 char *mesg;
 char *want;
} cnf;
// Single rotor bumpf
typedef struct {
 byte pos, notch0, notch1;
 byte *fwd, Fbuf[26*3]; // triple copy of fwd mappings
 byte *rev, Rbuf[26*3]; // triple copy of rev mappings
} rotor_t; // fwd & rev mappings, current position & notch(es)
// Device state
struct deviceState {
 bool isM4; // true if configured as M4 (4 rotor)
 bool m4m3; // true if M4 config'd equivalent to M3 (not used)
 byte *pplug, pgbBuf[26*3]; // Plugboard ptr and its buffer
 byte *prflc, refBuf[26*3]; // Reflector ptr and its buffer
 rotor_t Q, L, M, R; // 'Quad', left, middle, and right
} d;
/* USEFUL GLOBALS */
char tbuf[26*3]; // General purpose
byte mdArr[26*3], *md; // Wrap around modulo conversion
byte AtoN[128]; // conversion: A-Za-z >> 0-25
char *NtoA = rotors[0]; // conversion: 0-25 >> A-Z
// Replicate leading 26 bytes twice more. Return ptr to 2nd copy
byte *triple( byte *p ) {
 memcpy( p + 26, p, 26 );
 memcpy( p + 52, p, 26 );
 return p + 26;
}
// Push a single char through Enigma's components
char convert( char in ) {
 if( !isalpha( in ) ) return in; // only translate alphabet chars
#define AtNotch(x) (x.pos==x.notch0 || x.pos==x.notch1) // Test one byte as matching one of two.
 if( AtNotch( d.M ) ) d.M.pos++, d.L.pos++; // advance left and middle rotor
 else if( AtNotch( d.R ) ) d.M.pos++; // or maybe just the middle
 d.R.pos++; // always advance right rotor
#undef AtNotch
 d.R.pos = md[ d.R.pos ]; // Ensure modulo wrap around
 d.M.pos = md[ d.M.pos ];
 d.L.pos = md[ d.L.pos ];
// d.Q.pos = md[ d.Q.pos ]; // consistent, but never rotates
#define FWD( r ) ( r.fwd[ x + r.pos ] - r.pos )
#define REV( r ) ( r.rev[ x + r.pos ] - r.pos )
 int x = d.pplug[ AtoN[ in ] ];
 x = FWD( d.R );
 x = FWD( d.M );
 x = FWD( d.L );
 x = FWD( d.Q );
 x = d.prflc[ x ];
 x = REV( d.Q );
 x = REV( d.L );
 x = REV( d.M );
 x = REV( d.R );
 return NtoA[ d.pplug[ x ] ];
#undef FWD
#undef REV
}
/* Helper functions for setupDevice() */
// Assemble one working rotor: its useable mappings, notch(es) and initial position
void prepRotor( int rn, rotor_t *px ) {
 char r = cnf.sqnc[ rn ];
 byte adj = AtoN[ cnf.ring[ rn ] ]; // ring setting applied in this func
 // 4th rotor is '0' ("NULL rotor") or else 'B'eta or 'G'amma
 int idx = isdigit(r) ? r-'0' : r=='B'? 9 : 10; // index of desired mapping string
 char *pUse = strcpy( tbuf, rotors[ idx ] ); // copy all 29+ characters
 px->notch0 = AtoN[ pUse[ 28 ] ]; // store the notch position(s)
 px->notch1 = AtoN[ pUse[ 29 ] ];
 pUse = (char*)triple( (byte*)pUse ); // copy rotor's mapping (only need two, but...)
 pUse -= adj; // adjust ptr by ring setting
 // pUse now points somewhere within tbuf[1 to 26]
 // pUse[0 to 25] is the range of 26 (wrap around) mappings accounting for ring setting
 // populate 26 elements (as int) of the adjusted fwd & rev mapping arrays
 for( byte i = 0; i < 26; i++ ) {
 byte x = md[ AtoN[ pUse[ i ] ] + adj ]; // undo ring setting and adjust modulo
 px->Fbuf[ i ] = x; // fwd mapping
 px->Rbuf[ x ] = i; // rev mapping
 }
 px->fwd = triple( px->Fbuf );
 px->rev = triple( px->Rbuf );
 px->pos = AtoN[ cnf.init[ rn ] ]; // Initial Rotor position
}
// Assemble the desired reflector (one of four possibles)
void prepReflect( void ) {
 bool isB = cnf.refID == 'B';
 char *cp = d.isM4 ? isB ? reflBthin : reflCthin : isB ? reflB : reflC;
 for( int i = 0; i < 26; i++ ) d.refBuf[i] = AtoN[cp[i]]; // Copy reflector chars as ints
 d.prflc = triple( d.refBuf );
}
// If there is ZERO difference in these letters, then 'yes' this M4 is working as an M3
bool M4asM3( char *v ) {
 return 0 == (cnf.sqnc[0]-v[0]) + (cnf.refID-v[1]) + (cnf.init[0]-'A') + (cnf.ring[0]-'A' );
}
// Build components and configure device from configuration info
void setupDevice( void ) {
 int i;
 d.isM4 = isalpha( cnf.sqnc[0] ) ? true : false; // Beta or Gamma indicates M4 (vs. "1-8")
 d.m4m3 = d.isM4 && ( M4asM3( "BB" ) || M4asM3( "GC" ) );
// PlugBoard
 // Copy up to 20 alpha-only chars from config str; stop if encountering '0円'
 for( char *sp = cnf.swap, *dp = tbuf; dp - tbuf < 20 && *sp; sp++ )
 if( isalpha( *sp ) )
 *dp++ = *sp;
 *dp = '0円';
 // Initialise with ascending ints
 for( i = 0; i < 26; i++ ) d.pgbBuf[i] = (byte)i;
 for( sp = tbuf; sp[0] && sp[1]; sp += 2 ) { // Swap pairs of plugboard letters as per config
 int x = AtoN[sp[0]], y = AtoN[sp[1]];
 byte tmp = d.pgbBuf[x]; d.pgbBuf[x] = d.pgbBuf[y]; d.pgbBuf[y] = tmp;
 }
 d.pplug = triple( d.pgbBuf );
// Set up 4 rotors with Ringsettings
 prepRotor( 3, &d.R ); // right rotor is last in sequence
 prepRotor( 2, &d.M ); // middle rotor is 2nd last
 prepRotor( 1, &d.L ); // etc.
 prepRotor( 0, &d.Q ); // sometimes simply "pass thru" for m3 operation
// Set up the selected reflector
 prepReflect();
}
// Digest one region of configuration info
void digestCfg( char *buf, char *eob ) {
 char *tags[] = { "m mesg", "w want", "c cnfg" }; // used in file
 const int nTag = sizeof tags/sizeof tags[0];
 cnf.swap = "";
 char *thisSqnc = "123", *thisRing = "AAAA", *thisInit = "AAAA"; // defaults; only 3chars for sqnc mean M3
 int i;
 for( char *lcp = buf, *ep = buf; lcp < eob; lcp = ep ) {
 ep = markEOL( ep );
 printf( "%s\n", lcp );
 // tidy up the input ahead of 'interpretting'
 while( lcp && isspace( *lcp ) ) lcp++; // skip leading whitespace
 for( char *cp = lcp; *cp && *cp != '#'; cp++ ) {} // scan for comment marker
 if( *cp == '#' ) *cp = '0円'; // truncate a comment
 if( lcp[0] == '0円' ) continue; // Ignore empty lines
 char val = '?';
 for( i = 0; i < nTag && val == '?'; i++ ) // try to match known tag
 if( strncmp( lcp, &tags[i][2], strlen( &tags[i][2] ) ) == 0 )
 val = tags[i][0]; // A single char token for this text tag
 if( ( cp = strchr( lcp, '=' ) ) != NULL ) cp++; // Line up on what follows '='
 if( val == 'm' ) cnf.mesg = cp;
 else if( val == 'w' ) cnf.want = cp;
 else if( val == 'c' ) {
 for( i = 0; i < 4; i++ ) {
 if( ( cp = strtok( cp, " " ) ) == NULL ) fprintf( stderr, "Too short\n" ), exit(1);
 if( i == 0 ) cnf.refID = cp[0];
 else if( i == 1 ) thisSqnc = cp;
 else if( i == 2 ) thisRing = cp;
 else thisInit = cp, cnf.swap = cp + strlen( cp ) + 1; // Jump past termination
 cp = NULL;
 }
 } else fprintf( stderr, "Garbled input >>%s<<\n", lcp ), exit(1);
 }
 i = 0;
 if( strlen( thisSqnc ) == 3 ) cnf.sqnc[i] = '0', cnf.ring[i] = 'A', cnf.init[i++] = 'A';
 memcpy( &cnf.sqnc[i], thisSqnc, 4 - i );
 memcpy( &cnf.ring[i], thisRing, 4 - i );
 memcpy( &cnf.init[i], thisInit, 4 - i );
}
// invoke char-by-char cipher mapping of the message string
void cipherMsg( char *pMsg ) {
 fprintf( stderr, "\n<<<:%s\n", pMsg );
 for( int i = 0; ( pMsg[i] = convert( pMsg[i] ) ) != '0円'; i++ ) {}
 fprintf( stderr, ">>>:%s\n", pMsg );
 printf( "\tmesg=%s#### After processing\n", pMsg );
}
int main( int ac, char **av ) {
 if( ac != 2 ) {
 fprintf( stderr, "Usage: %s filename\n", av[0] );
 return 1;
 }
 // initialise useful lookup arrays (instead of compile time blah-blah-blah)
 memset( AtoN, '~', sizeof AtoN ); // Color elements to be "unused" value
 for( byte i = 0; i < 26; i++ )
 mdArr[i] = AtoN['A'+i] = AtoN['a'+i] = i;
 md = triple( mdArr );
 // Load the input file and dice it into segments
 char *bndry;
 seg_t *pRoot = loadSegments( av[1], bndry = "[-SNIP-]" );
 // redirect to related file: "X0.txt" >> "X1.txt"
 char *cp = strrchr( av[1], '.' );
 cp[-1]++;
 freopen( av[1], "wt", stdout );
 // process each segment individually
 for( seg_t *pX = pRoot; pX; pX = pX->nxt ) {
 digestCfg( pX->begin, pX->end );
 setupDevice();
 cipherMsg( cnf.mesg );
 if( pX->nxt ) printf( "\n\n%s\n", bndry );
 }
 free( pRoot->begin );
 while( ( pX = pRoot ) != NULL )
 pRoot = pX->nxt, free( pX );
 return 0;
}

Sample configuration file:
For the sake of simplicity, the "message" must be all on one line in this file." Future development??

# 1: From a website
# rf | sqn | rng | ini | plgbrd
 cnfg=B 123 AAA ABC =
 mesg=AEFAEJXXBNXYJTY
 want=CONGRATULATIONS
[-SNIP-]
# 2: From a website
# rf | sqn | rng | ini | plgbrd
 cnfg=B 123 AAA ABR =
 mesg=MABEKGZXSG
 want=TURNMIDDLE
[-SNIP-]
# 3: From a website
# rf | sqn | rng | ini | plgbrd
 cnfg=B 123 AAA ADS =
 mesg=RZFOGFYHPL
 want=TURNSTHREE
[-SNIP-]
# 4: From a website
# rf | sqn | rng | ini | plgbrd
 cnfg=B 123 JNU XYZ =
 mesg=QKTPEBZIUK
 want=GOODRESULT
[-SNIP-]
# 5: From a website
# rf | sqn | rng | ini | plgbrd
 cnfg=B 123 JNU VQQ AP BR CM FZ GJ IL NT OV QS WX
 mesg=HABHV HL YDFN ADZY
 want=THATS IT WELL DONE
[-SNIP-]
# QBF
# rf | sqn | rng | ini | plgbrd
 cnfg=B 125 JNK ABP AM BN CO DP EQ FR GS HT IU JV
 mesg=THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
 want=BZR YNYFG AJJTY CRE VDGAM FCXQ AZB WWVN HMY
[-SNIP-]
# YouTube MS-Excel Enigma
# rf | sqn | rng | ini | plgbrd
 cnfg=B 321 LDX ADO =
 mesg=ZEFUSV WZX QOGHUYJO!
 want=THANKS FOR WATCHING!
[-SNIP-]
# YouTube MS-Excel Enigma - response
# rf | sqn | rng | ini | plgbrd
 cnfg=B 321 LDX ADO =
 mesg=VEXNOM LSWK VFKJ: HXJEY TTGXB LXGZNYSAE
 want=SHOULD HAVE USED: DANKE FUERS ZUSCHAUEN
[-SNIP-]
# An M4 test
# rf | sqnc | ring | init | plgbrd
 cnfg=B B678 DAMN HELL AQ BZ DP FM GO HW IJ KY LV NR
 mesg=H PBNZ L YGQ OVIA M XZN HQZB Q VHWQJX Z YKA MLTY U VX DZU
 want=I WISH I WAS WHAT I WAS WHEN I WISHED I WAS WHAT I AM NOW
[-SNIP-]
# QBF using M3 'B' reflector
# M3 r | sqn | rng | ini | plgbrd
 cnfg=B 531 CHE HIN AB CD EF GH IJ KL MN OP QR ST
 mesg=THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
 want=VAR PLLZC ZUUGP BLL ZTCVL GYAB BRV QEWU SVX
[-SNIP-]
# QBF using M3 'C' reflector
# M3 r | sqn | rng | ini | plgbrd
 cnfg=C 246 CHE HIN BA DC FE HG JI LK NM PO RQ TS
 mesg=THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
 want=WVQ SKNRG FPEQX ITN VQCWU QRIX YLS GVWF TBH
[-SNIP-]
# QBF using M4 'B' reflector -- NOT compatible with M3 by not using AA for ring&init
# M4 r | sqnc | ring | init | plgbrd
 cnfg=B B543 RCAQ CHIB OV UM AY
 mesg=THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
 want=XAW POOIT HYIYM GMR OZORT XIHV CGJ FTEP YWH
Toby Speight
87.9k14 gold badges104 silver badges325 bronze badges
asked Apr 26, 2023 at 6:01
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Perhaps some named constants in place of the magic numbers and more verbose variable names? \$\endgroup\$ Commented Apr 27, 2023 at 9:01

1 Answer 1

6
\$\begingroup\$

Fails to compile for me - looks like a simple spelling error:

gcc-12 -std=c17 -fPIC -gdwarf-4 -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Wmissing-braces -Wconversion -Wstrict-prototypes -fanalyzer 284674.c -o 284674
284674.c:7:10: fatal error: sys\stat.h: No such file or directory
 7 | #include <sys\stat.h>
 | ^~~~~~~~~~~~

After correcting to <sys/stat.h>, I get errors because we're missing an include of <stdbool.h>. Adding that, the code is still uncompilable:

284674.c:122:14: error: initializer element is not constant
 122 | char *NtoA = rotors[0]; // conversion: 0-25 >> A-Z
 | ^~~~~~
284674.c: In function ‘setupDevice’:
284674.c:219:6: error: ‘dp’ undeclared (first use in this function); did you mean ‘d’?
 219 | *dp = '0円';
 | ^~
 | d
284674.c:219:6: note: each undeclared identifier is reported only once for each function it appears in
284674.c:224:10: error: ‘sp’ undeclared (first use in this function)
 224 | for( sp = tbuf; sp[0] && sp[1]; sp += 2 ) { // Swap pairs of plugboard letters as per config
 | ^~
284674.c: In function ‘digestCfg’:
284674.c:256:14: error: ‘cp’ undeclared (first use in this function); did you mean ‘ep’?
 256 | if( *cp == '#' ) *cp = '0円'; // truncate a comment
 | ^~
 | ep
284674.c: In function ‘main’:
284674.c:324:14: error: ‘pX’ undeclared (first use in this function)
 324 | while( ( pX = pRoot ) != NULL )
 | ^~

Even after correcting all these, many warnings remain:

gcc-12 -std=c17 -fPIC -gdwarf-4 -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Wmissing-braces -Wconversion -Wstrict-prototypes -fanalyzer 284674.c -o 284674
284674.c: In function ‘loadFile’:
284674.c:30:46: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘__off_t’ {aka ‘long int’} may change the sign of the result [-Wsign-conversion]
 30 | if( ( buf = (char*)malloc( finfo.st_size + 1) ) == NULL ) fprintf( stderr, "Malloc() failed\n" ), exit(1);
 | ~~~~~~~~~~~~~~^~~
284674.c:32:29: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘__off_t’ {aka ‘long int’} may change the sign of the result [-Wsign-conversion]
 32 | if( fread( buf, 1, finfo.st_size, fp ) != (size_t)finfo.st_size ) fprintf( stderr, "Read incomplete\n" ), exit(1);
 | ~~~~~^~~~~~~~
284674.c: At top level:
284674.c:84:21: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 84 | char *reflB = "YRUHQSLDPXNGOKMIEBFZCWVJAT";
 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
284674.c:85:21: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 85 | char *reflC = "FVPJIAOYEDRZXWGCTKUQSBNMHL";
 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
284674.c:86:21: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 86 | char *reflBthin = "ENKQAUYWJICOPBLMDXZVFTHRGS"; // M4 only
 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
284674.c:87:21: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 87 | char *reflCthin = "RDOBJNTKVEHMLFCWZAXGYIPSUQ"; // M4 only
 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
284674.c: In function ‘convert’:
284674.c:148:26: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 148 | int x = d.pplug[ AtoN[ in ] ];
 | ^
284674.c: In function ‘prepRotor’:
284674.c:167:30: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 167 | byte adj = AtoN[ cnf.ring[ rn ] ]; // ring setting applied in this func
 | ~~~~~~~~^~~~~~
284674.c:173:28: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 173 | px->notch0 = AtoN[ pUse[ 28 ] ]; // store the notch position(s)
 | ~~~~^~~~~~
284674.c:174:28: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 174 | px->notch1 = AtoN[ pUse[ 29 ] ];
 | ~~~~^~~~~~
284674.c:183:32: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 183 | byte x = md[ AtoN[ pUse[ i ] ] + adj ]; // undo ring setting and adjust modulo
 | ~~~~^~~~~
284674.c:190:29: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 190 | px->pos = AtoN[ cnf.init[ rn ] ]; // Initial Rotor position
 | ~~~~~~~~^~~~~~
284674.c: In function ‘prepReflect’:
284674.c:198:56: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 198 | for( int i = 0; i < 26; i++ ) d.refBuf[i] = AtoN[cp[i]]; // Copy reflector chars as ints
 | ~~^~~
284674.c: In function ‘setupDevice’:
284674.c:212:34: warning: passing argument 1 of ‘M4asM3’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 212 | d.m4m3 = d.isM4 && ( M4asM3( "BB" ) || M4asM3( "GC" ) );
 | ^~~~
284674.c:203:20: note: expected ‘char *’ but argument is of type ‘const char *’
 203 | bool M4asM3( char *v ) {
 | ~~~~~~^
284674.c:212:52: warning: passing argument 1 of ‘M4asM3’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 212 | d.m4m3 = d.isM4 && ( M4asM3( "BB" ) || M4asM3( "GC" ) );
 | ^~~~
284674.c:203:20: note: expected ‘char *’ but argument is of type ‘const char *’
 203 | bool M4asM3( char *v ) {
 | ~~~~~~^
284674.c:217:30: warning: pointer targets in initialization of ‘unsigned char *’ from ‘char *’ differ in signedness [-Wpointer-sign]
 217 | for (unsigned char *sp = cnf.swap; dp - tbuf < 20 && *sp; sp++ )
 | ^~~
284674.c:219:21: warning: conversion to ‘char’ from ‘unsigned char’ may change the sign of the result [-Wsign-conversion]
 219 | *dp++ = *sp;
 | ^
284674.c:226:24: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 226 | int x = AtoN[sp[0]], y = AtoN[sp[1]];
 | ~~^~~
284674.c:226:41: warning: array subscript has type ‘char’ [-Wchar-subscripts]
 226 | int x = AtoN[sp[0]], y = AtoN[sp[1]];
 | ~~^~~
284674.c: In function ‘digestCfg’:
284674.c:243:22: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 243 | char *tags[] = { "m mesg", "w want", "c cnfg" }; // used in file
 | ^~~~~~~~
284674.c:243:32: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 243 | char *tags[] = { "m mesg", "w want", "c cnfg" }; // used in file
 | ^~~~~~~~
284674.c:243:42: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 243 | char *tags[] = { "m mesg", "w want", "c cnfg" }; // used in file
 | ^~~~~~~~
284674.c:246:14: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 246 | cnf.swap = "";
 | ^
284674.c:247:22: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 247 | char *thisSqnc = "123", *thisRing = "AAAA", *thisInit = "AAAA"; // defaults; only 3chars for sqnc mean M3
 | ^~~~~
284674.c:247:41: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 247 | char *thisSqnc = "123", *thisRing = "AAAA", *thisInit = "AAAA"; // defaults; only 3chars for sqnc mean M3
 | ^~~~~~
284674.c:247:61: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 247 | char *thisSqnc = "123", *thisRing = "AAAA", *thisInit = "AAAA"; // defaults; only 3chars for sqnc mean M3
 | ^~~~~~
284674.c:283:39: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-conversion]
 283 | memcpy( &cnf.sqnc[i], thisSqnc, 4 - i );
 | ~~^~~
284674.c:284:39: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-conversion]
 284 | memcpy( &cnf.ring[i], thisRing, 4 - i );
 | ~~^~~
284674.c:285:39: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-conversion]
 285 | memcpy( &cnf.init[i], thisInit, 4 - i );
 | ~~^~~
284674.c: In function ‘main’:
284674.c:310:47: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
 310 | seg_t *pRoot = loadSegments( av[1], bndry = "[-SNIP-]" );
 | ^
284674.c: In function ‘mkSgmt’:
284674.c:48:14: warning: dereference of possibly-NULL ‘p’ [CWE-690] [-Wanalyzer-possible-null-dereference]
 48 | p->begin = from;
 | ~~~~~~~~~^~~~~~
 ‘mkSgmt’: events 1-2
 |
 | 47 | seg_t *p = (seg_t*)malloc( sizeof *p );
 | | ^~~~~~~~~~~~~~~~~~~
 | | |
 | | (1) this call could return NULL
 | 48 | p->begin = from;
 | | ~~~~~~~~~~~~~~~ 
 | | |
 | | (2) ‘p’ could be NULL: unchecked value from (1)
 |
284674.c: In function ‘loadSegments’:
284674.c:60:33: warning: use of NULL ‘ep’ where non-null expected [CWE-476] [-Wanalyzer-null-argument]
 60 | for( char *ep = buf; ( ep = strstr( ep, pBndry ) ) != NULL; ) {
 | ^~~~~~~~~~~~~~~~~~~~
 ‘loadSegments’: events 1-2
 |
 | 54 | seg_t *loadSegments( char *fName, char *pBndry ) {
 | | ^~~~~~~~~~~~
 | | |
 | | (1) entry to ‘loadSegments’
 | 55 | char *eob, *buf = loadFile( fName, &eob );
 | | ~~~~~~~~~~~~~~~~~~~~~~~
 | | |
 | | (2) calling ‘loadFile’ from ‘loadSegments’
 |
 +--> ‘loadFile’: events 3-11
 |
 | 22 | char *loadFile( char *fname, char **pEOB ) {
 | | ^~~~~~~~
 | | |
 | | (3) entry to ‘loadFile’
 | 23 | struct stat finfo;
 | 24 | if( stat( fname, &finfo ) != 0 ) fprintf( stderr, "Cannot see '%s'\n", fname ), exit(1);
 | | ~
 | | |
 | | (4) following ‘false’ branch...
 |......
 | 27 | if( ( fp = fopen( fname, "rb" ) ) == NULL ) fprintf( stderr, "Cannot open '%s'\n", fname ), exit(1);
 | | ~ ~~~~~~~~~~~~~~~~~~~~
 | | | |
 | | | (5) ...to here
 | | (6) following ‘false’ branch (when ‘fp’ is non-NULL)...
 |......
 | 30 | if( ( buf = (char*)malloc( finfo.st_size + 1) ) == NULL ) fprintf( stderr, "Malloc() failed\n" ), exit(1);
 | | ~ ~~~~~~~~~~~~~
 | | | |
 | | | (7) ...to here
 | | (8) following ‘false’ branch (when ‘buf’ is non-NULL)...
 | 31 | 
 | 32 | if( fread( buf, 1, finfo.st_size, fp ) != (size_t)finfo.st_size ) fprintf( stderr, "Read incomplete\n" ), exit(1);
 | | ~ ~~~~~~~~~~~~~
 | | | |
 | | | (9) ...to here
 | | (10) following ‘false’ branch...
 | 33 | fclose( fp );
 | | ~~~~~~~~~~~~
 | | |
 | | (11) ...to here
 |
 <------+
 |
 ‘loadSegments’: event 12
 |
 | 55 | char *eob, *buf = loadFile( fName, &eob );
 | | ^~~~~~~~~~~~~~~~~~~~~~~
 | | |
 | | (12) returning to ‘loadSegments’ from ‘loadFile’
 |
 ‘loadSegments’: events 13-16
 |
 | 60 | for( char *ep = buf; ( ep = strstr( ep, pBndry ) ) != NULL; ) {
 | | ^
 | | |
 | | (13) following ‘true’ branch (when ‘ep’ is non-NULL)...
 | 61 | pTail->end = ep - 1;
 | | ~~~~~~ 
 | | |
 | | (14) ...to here
 | 62 | if( (ep = strchr( ep, '\n' ) ) != NULL )
 | | ~ ~~~~~~~~~~~~~~~~~~ 
 | | | |
 | | | (15) when ‘strchr’ returns NULL
 | | (16) following ‘false’ branch (when ‘ep’ is NULL)...
 |
 ‘loadSegments’: event 17
 |
 |cc1:
 | (17): ...to here
 |
 ‘loadSegments’: event 18
 |
 | 60 | for( char *ep = buf; ( ep = strstr( ep, pBndry ) ) != NULL; ) {
 | | ^~~~~~~~~~~~~~~~~~~~
 | | |
 | | (18) argument 1 (‘ep’) NULL where non-null expected
 |
In file included from 284674.c:6:
/usr/include/string.h:350:14: note: argument 1 of ‘strstr’ must be non-null
 350 | extern char *strstr (const char *__haystack, const char *__needle)
 | ^~~~~~

I recommend addressing these, too.


General impression is that the code is very hard to read, to the point of being unmaintainable. The identifiers are very cryptic, and while that might be in keeping with the subject matter, it really hampers understanding of the code.

It's a well-established principle that control statements are best used with a block rather than a single statement, to avoid introducing errors. For example, I'd re-write

 FILE *fp;
 if( ( fp = fopen( fname, "rb" ) ) == NULL ) fprintf( stderr, "Cannot open '%s'\n", fname ), exit(1);
 char *buf;
 if( ( buf = (char*)malloc( finfo.st_size + 1) ) == NULL ) fprintf( stderr, "Malloc() failed\n" ), exit(1);

more readably as

 FILE *const fp = fopen(fname, "rb");
 if (!fp) {
 perror(fname);
 exit(EXIT_FAILURE);
 }
 char *const buf = malloc(finfo.st_size + 1);
 if (!buf) {
 fputs("malloc() failed\n", stderr);
 exit(EXIT_FAILURE);
 }

Throughout the code, we have the standard <ctype> bug of using its functions with plain char argument. Never do that - always convert to unsigned char before the argument is promoted to int. In some places, we can do this simply by using unsigned char* in place of char* for a pointer type.


There's a portability problem here:

 for( byte i = 0; i < 26; i++ )
 mdArr[i] = AtoN['A'+i] = AtoN['a'+i] = i;

Not all execution character sets encode letters consecutively - for example, this logic will yield "abcdefghi«»ðýþ±°jklmnopqra" for the lowercase letters on an EBCDIC system.

answered Apr 26, 2023 at 7:45
\$\endgroup\$
2
  • \$\begingroup\$ Wow! Much appreciated feedback! I did mention that I'm using an ancient (C++) compiler. Obviously it is not as stringent as modern compilers with their helpful commentary and insights... Again, thank you for the feedback. I'm ancient, too, but not as ancient as EBCDIC... :-) \$\endgroup\$ Commented Apr 26, 2023 at 7:58
  • 2
    \$\begingroup\$ The compiler is evidently somewhat buggy, especially as regards the scope of variables introduced in a for control statement. I recommend you get hold of a decent C compiler such as GCC or Clang (which both come with good modern C++ compilers as a useful bonus). Even if you're on Windows, I believe there are good free-software compilers available. \$\endgroup\$ Commented Apr 26, 2023 at 8:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.