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
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Perhaps some named constants in place of the magic numbers and more verbose variable names? \$\endgroup\$
    – Harith
    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«»ðýþ±°jklmnopqrª" for the lowercase letters on an EBCDIC system.

\$\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\$
    – Fe2O3
    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

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.