/* ** check_char() ** An ANSI C function which, given a key (optionally without a check ** character) returns a check character using the Brown University ** Women Writers Project name key check algorithm. ** ** Copyright 1997 by Syd Bauman and Brown University. ** This subroutine 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 2 of the ** License, or (at your option) any later version. ** This subroutine 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 file; if not, write to the ** Free Software Foundation, Inc. ** 675 Mass Ave ** Cambridge, MA 02139 ** USA ** gnu@prep.ai.mit.edu. ** ** Credits ** ------- ** Original concept: John Lavagnino ** Details: WWP encoding staff ** Revised concept: Syd Bauman ** Algorithm: Norman Bauman ** Coding: Syd Bauman ** ** revision hx: ** -------- -- ** 1997-05-29 by Syd Bauman: This file created by yanking ** the check_char() function and needed header statements ** out of the key4kwelbo source code. Tested with the ** gcc compiler by writing a quick mainline. */ /* ** Header stuff, which you will probably want to have in ** your .h file instead. */ char check_char( char *key ); #include #include #define EKEYLEN 14 #define DOT '.' const char *alpha = "abcdefghijklmnopqrstuvwxyz*+-"; /* ------------------------ */ /* ----- check_char() ----- */ /* ------------------------ */ /* ** parameters: a pointer to a character array which should hold a 'key', ** with or without the check character. I.e., an array of mixed-case ** characters with the following format: ** * 0-9 letters (see note [1]) ** * a period ** * 2 or 3 letters (see note [2]) ** * NULL ** The contents of this array are not altered. ** input: none ** output: none ** returns: a character, one of the following: ** * a letter of the alphabet, which is the appropriate check letter ** for the input key, according to the WWP key check algorithm. ** * an asterisk ("*"), plus sign ("+"), or hyphen ("-"), which is the ** inappropriate check character for the input key, according to the ** WWP key check algorithm. That is, the character is the correct ** check character, but since it's > 25 (thus non-alphabetic), we ** don't want to use it. ** * a NULL, indicating an error. ** side effects: none ** ** Notes ** ----- ** [1] There really shouldn't be 0 characters before the period, but we ** don't check for this condition, and it doesn't cause a problem ** when it happens -- we just return the proper check character for ** the two disambiguation letters preceded by nine asterisks! ** [2] All non-null bytes after the 2nd letter after the period are ** dutifully copied, but are not used. Therefore, extra non-NULL ** characters up to a total length of EKEYLEN will have no effect on ** the result; any more than that, though will cause an ** out-of-bounds error condition, which could be catastrophic. */ char check_char( char *key ) { short i = 0, /* input index & temporary loop counter */ o = 0, /* output index */ u = 0; /* check didgit calculation holder */ char expanded_key[EKEYLEN]; /* buffer for expanded version of key used for check char calculation */ /* ** Create the "expanded initial portion" of the key. This is done by ** taking the initial portion (those letters before the dot), ** translating them to lower case, and padding them on the right with ** asterisks to a length of 9 characters, then tacking on the remaining ** two disambiguation characters. (The following loop will actually also ** tack on the final check character, too, but it is not used below.) */ while ( key[i] != NULL ) { /* ** If this character is a letter, copy ** lower case version over. */ if ( isalpha( key[i] ) ) { expanded_key[o++] = tolower( key[i++] ); } /* ** If it is a dot, copy asterisks over until ** we've reached the desired length of 9 characters. */ else if ( key[i] == DOT ) { while ( o < 9 ) expanded_key[o++] = '*'; ++i; /* don't forget to go to next input character */ } /* ** If it's not a letter or a dot, it's an error. */ else return( NULL ); } /* ** It is not necessary to null-terminate the expanded key, but ** it's a good idea lest we change this code someday; furthermore, ** it makes debugging a lot easier. */ expanded_key[o] = NULL; /* ** Now actually do the calculation. ** ** for each of the 11 characters (1 first initial, 8 surname, 2 ** disambiguation; or 8 name, 1 extra asterisk, 2 disambiguation), ** convert it to a number base 27 (a=0, b=1, ... z=25, *=26), multiply ** by 27 (so that the last character is the ones digit, the 2nd to ** rightmost is the 27s digit, the 3rd to rightmost is the 729s digit, ** etc.), and knock the result down mod 29. We could wait until the end ** of the loop take take the whole thing mod 29, except that we would ** have overflowed long before then. */ for( i = 0; i < 11; i++ ) { u += (strchr( alpha, expanded_key[i] ) - alpha ); u *= 27; u %= 29; } u = 56 - u; /* 56 is a magic constant */ u %= 29; /* knock down by 29 again */ return( alpha[u] ); }