Difference between revisions of "VotingMachine2.02"

From SLSA
Jump to navigation Jump to search
(Created page with "This is the main voting machine script. It is heavily commented throughout to explain its workings. The script will even give a copy of itself form the voting machine during...")
 
m
 
Line 1: Line 1:
 
This is the main voting machine script.  It is heavily commented throughout to explain its workings.  The script will even give a copy of itself form the voting machine during an election, if requested, so that the script in the voting machine can be compared with the script below to ensure that it has not been tampered with.
 
This is the main voting machine script.  It is heavily commented throughout to explain its workings.  The script will even give a copy of itself form the voting machine during an election, if requested, so that the script in the voting machine can be compared with the script below to ensure that it has not been tampered with.
 +
 +
[[Vote Machine]]
  
 
<syntaxhighlight lang="lsl2">
 
<syntaxhighlight lang="lsl2">

Latest revision as of 04:04, 27 January 2014

This is the main voting machine script. It is heavily commented throughout to explain its workings. The script will even give a copy of itself form the voting machine during an election, if requested, so that the script in the voting machine can be compared with the script below to ensure that it has not been tampered with.

Vote Machine

// designed and written by Sally LaSalle. version 2.0 June 2011.  (previous version 1.0.6 may 2010)
// this code may be used for non-profit purposes only
// this code will be made available to the SL Scripting Library as open source so that there is no
// commercial value in the script.
//
// purpose: A Voting Machine which has the following capabilities
// 1. TOTAL AND COMPLETE VOTER ANONYMITY (no-one other than the voter themselves can EVER know who they voted for.
// 2. NO RESULTS CAN BE SEEN UNTIL THE ELECTION CLOSE. (not even by the officials or voting machine owner)
// 3. ONLY ELIGIBLE VOTERS CAN VOTE (voters can check their eligibility with the machine prior to election opening)
// 4. VOTERS CAN ONLY VOTE ONCE. (and cannot change their vote once it is confirmed by the voter)
// 5. VOTERS CAN CONFIRM THEIR OWN VOTE BEFORE LOCKING IT IN. (ensuring that they can correct a voting error before it is locked in)
// 6. VOTERS RECEIVE A ONE-DIRECTION VOTER ENCRYPTED CODE (VEC), TOGETHER WITH THEIR VOTE IN A PRIVATE IM TO THEM ONLY.
//    the VEC is created by joining the voter UUID, the vote machine UUID
//    and, a random number generated by the position of where the voter clicked on the box, these values are run through a one-directional cypher
//    one-directional meaning it cannot be decrypted. and it cannot be duplicated without knowledge of the random click position (which is impossible, even for the voter themselves)
// 7. VOTING OFFICIALS RECIEVE *ONLY* THE VEC, BUT NEVER THE VOTE!  (so that officials can validate the voting printout and ensure that all vote codes
//    on the printout originated from the official voting machine.
//    the officials will receive the VEC together with the UUID of the vote machine and the location of the machine
//    and also their own (and their own only) personal secret verification phrase
//    the officials will receive these codes only at the end of the the vote.
//    the official NEVER knows who voted for who ONLY that the voter is a legitimate voter from the official machine
// 8. AT ELECTION END, A LIST OF VEC'S TOGETHER WITH THE ASSOCIATED VOTE AND TALLIES IS PRINTED .
//    the voter can then confirm that the vote associated with their VEC is correct.
//    the LIST will be printed in code sorted order so that there is no time correlation possible for  which "code" voted in which sequence
//    (ie voter sequence to the machine cannot be recorded by an observer to determine which code is which voter)
//    (this can be printed by anyone AT ANY TIME AFTER THE ELECTION HAS CLOSED. to ensure that the votes in the official machine match the published result.
//    this is to prevent votes being "made up" and added to the tally outside of the machine.
// 9. VOTERS CAN RECEIVE A COPY OF THE SCRIPT AND CONFIGURATION CARDS DIRECTLY DURING THE VOTE.
//    this ensures that the published and checked script matches what is actually running in the machine and there has been no script tampering.
// 10. THIS SCRIPT HAS BEEN VERIFED BY MULTIPLE SCRIPTERS INCLUDING:-
//    , and random volunteers from the SL Advanced Scripting Group.
//    That it does in fact provide the capabilities listed in points 1 through 9 above and ONLY those capabilities.
//    Voters may also have the script checked with a scripter of their own choice to ensure that it meets the stated objectives in points 1 to 9

//   THE ELECTION PANEL'S ROLE HAS NOW BEEN REDUCED TO ONLY THE FOLLOWING
//   A) Answering questions of voter eligibility
//   B) Ensuring that all Codes printed on the election tally originated from the official machine.

// How it works ***********************************************************************
// The Voting machine consists of One main script (this script) and 5 Email Scripts (one for each election panel member)
// It contains a config notecard, which defines the open and closing time in UTC time coordinates, then number of positions, the election title, and help text
// It contains a choices notecard, which lists the choices candidates) available to the voter
// It contains a voters notecard, which lists the names of eligible voters.
// It contains an officials notecard, which lists the names, emails of the election panel
// It contains 5 pass notecards non readable non modifiable created by each member of the election panel. The phrase within each is sent to the official with the
// VEC list so that the official knows that the list comes from the machine that they placed their card in.
// When the machine is rezzed, it reads all the note cards and checks their validity.
// When the vote start time is passed, the machine goes in to voting mode and will accept votes.
// When a voter clicks on the machine they are given a menu with the list of candidates,
// The voter clicks on each of the candidates they want, until they have voted for all the vacant positions.
// The voter then receives a request to either confirm their vote or to start over.
// When the voter confirms their vote, their vote is added to the tally of their candidate choices, and a voter code is generated for that voter.
// the vote is saved next to the voter code so there is no correlation between voter and vote (only the voter can verify their vote)
// While one voter is voting, the machine is busy and another voter cannot start their vote until the previous voter has finished.
// If a voter crashes during their vote the machine will return to the vote open state after 2 minutes, the voter may try again later
// After the end time has passed, the machine prints out the Voter Code and Vote, and also the tally for each candidate.
// The officials receive an email with the Voter Codes only (not the vote), along with their secret pass phrase
//  so they can verify that the published codes originate from the voting machine
// Voters can click the machine to see that the voter code list matches the list published by the election officials.
//
// (The machine could also be used to record votes for constitutional amendments or other rule changes, in that case the "candidates" are "Yes" or "No")

// Program Map
// - Global Variable Declarations [lines  83 - 193]
// - Global Function Declarations [lines 193 - 286]
// - State Default                [lines 274 - 284]
// - State Voting Pending         [lines 289 - 386]
// - State Voting Open            [lines 390 - 428]
// - State Voting                 [lines 433 - 577]
// - State Voting Closed          [lines 579 - 620]
// - State Read Voters            [lines 625 - 669]
// - State Read Officials         [lines 671 - 717]
// - State Read Choices           [lines 719 - 803]
// - State Read Config            [lines 805 - 849]
// - State Read Authentication Cards [lines 850 - 926]

string version = "2.0.2 February 6 2012";
// program constants and variables ***************************************************

//  a 'strided' list of [voter names, and "has_voted"] (0=not voted, 1= voted), as determined from the names in the eligible voter list card
list voter_names = [];
integer number_of_eligible_voters=0;

// used for test harness code in voting open routine .
integer voter_index=0;

// a strided list of officials [firstname. lastname, email, uuid]
list officials = [];
integer number_of_officials=0;
// official_pass holds correlated "pass phrase" for each official
// each official places a card into the machine, the card contains a password or phrase only they know
// these cards are read and based on the creator of the card .. stored in the postiion in this list corresponding to that official
// when the list of valid codes is sent to the official, their private passcode is sent also to ensure that the transmission originated
// from the official voting machine
list official_pass = ["","","","",""];

// a list of the candidate choices, for the voting menu
list choices = [];
integer number_of_choices=0;
// tally is a correlated list keeping vote counts for each choice in the choices list
list tally = [0,0,0,0,0,0,0,0,0];


// items read from the configuration card
string voting_start_time;
list vst_elements;
string voting_end_time;
list vet_elements;
// number of positions (answers) required from each voter
integer number_of_positions;
string vote_title;
string help_text;

// list to generate the menu the user sees along with the candidates
list voting_menu =["start over", "give scripts"];

// program control
// used for keeping track while reading the note cards
integer index;
integer position;
integer linecount;
key dataQueryID;
list query_ids =[];
string cardname="";

// number of "pass_" notecards in the voting machine
integer pass_cards=-1;
// number of pass notecards whose phrase has been read in
integer pass_cards_read=-1;

// notecard reading state
integer got_Config = FALSE;
integer got_Officials = FALSE;
integer got_Voters = FALSE;
integer got_Choices = FALSE;
integer got_Authentication_Card = FALSE;
// read subsections of config card
integer gotStart = FALSE;
integer gotEnd = FALSE;
integer gotTitle = FALSE;
integer gotHelp = FALSE;
integer gotPositions = FALSE;

integer lastVoterLine;

// menu listen channel will be a randomly assigned negative channel ID to prevent inteference from external objects
integer menu_channel;
integer hMenuListen;
// new owner channel handles to allow the vote machine owner to check state and eligible voter lists and to add an eligible voter who was left off
integer owner_channel = 999666;
integer hOwnerListen;

// timer control "constants"
float ONE_MINUTE = 60.0;
float TWO_MINUTES= 120.0;
float FIVE_MINUTES= 300.0;
float TIMER_OFF= 0.0;

// which state is the machine currently in
integer machine_status;
integer DEFAULT = 0;
integer VOTING_PENDING = 1;
integer VOTING_OPEN = 2;
integer VOTING = 3;
integer VOTING_CLOSED = 4;
integer READING = 5;
integer ERROR = -1;
integer DEBUG = FALSE;
integer RESET = -1;
// Message codes for the memory sub-script 
integer MCHOICES = 200;
integer MTALLY = 210;
integer MOUTPUT = 230;
integer MPOSITIONS=240;
integer MVEC = 250;
integer MEND = 260;

// for floating text
string machine_state;

// uuid key of the current voter, used to send private IMs with instructions and choices.
key voter_key;
// vote list, holds a temporary list of pointers to each of the voters selections, during their voting session
list vote;
// votenames list, holds a temporary list of voter choice names, during their voting session
list votenames;
// name of the voter
string voter_name;
// a pointer to the position in the list of eligible voters, where the current voters name is
integer voter_record_position;
// the unique code generate for each voter that votes,
string voter_encryption_code;

// 
integer menu_position=0;
// list to hold a randomized menu of candidates (randomized to avoid "donkey vote" bias)
list randomizedMenu;

// temporarily store the position the voter clicked on (used to calculate non-reversible voter code)
vector voter_click_position;

// voter encryption code and vote list
// [VEC, choice1, .. choiceN] where N = numbeer of positions
list voter_encryption_code_vote;

// how many voters have voted so far
integer voter_count;
// how many selections has the current voter made
integer vote_count;

// **********************************************************
// functions
integer CheckVoterEligibility(string voterName) {
    if (~ llListFindList(voter_names, (list)voterName)) {
        return TRUE;  // is in the list so is eligible
    } else {
        return FALSE;  // isnt in the list, so is not eleigible
    }
}

// returns true if the voter has not voted
integer CheckVoterNotVoted(string voterName) {
    integer listposition;
    listposition = llListFindList(voter_names, (list)voterName);
    if (~ listposition) {
        // flag field next to each voter name has either 0=not voted, or 1 = voted already
        if (llList2Integer(voter_names, listposition+1) == 1) {
            return FALSE;  // voting not allowed voter voted already
        } else {
            return TRUE;  // voting allowed, voter not voted yet
        }
    } else {
        return FALSE;  // voting not allowed cant find voter
    }
}

// checks if "timeBeingChecked" has been passed now
integer timeHasPassed(string timeBeingChecked) {
    // llGetTimeStamp returns current UTC time in form YYYY-MM-DDThh:mm:ss.ff..fZ
    list ltimenow = llParseString2List(llGetTimestamp(), ["-", "T", ":", "Z"], []);
    list ltbcheck = llParseString2List(timeBeingChecked, ["-", "T", ":", "Z"], []);
    integer timeUnit; // year, month, day, hour, minute, second
    for (timeUnit = 0; timeUnit < 6; ++timeUnit)
    {
        float fNowTimeUnit = llList2Float(ltimenow, timeUnit);
        float fChkTimeUnit = llList2Float(ltbcheck, timeUnit);
        if (fNowTimeUnit != fChkTimeUnit) {
            if (fNowTimeUnit < fChkTimeUnit)
                return FALSE;  // time being checked has not yet passed
            else
                return TRUE;  // time being chekced has passed!
        }
    }
    return TRUE;  // time being checked is right now !
}

// convert time unit seperated time stamp into number of seconds since unix time 0
integer uStamp2UnixInt( list vLstStp ){
    integer vIntYear = llList2Integer( vLstStp, 0 ) - 1902;
    integer vIntRtn;
    if (vIntYear >> 31 | vIntYear / 136){
        vIntRtn = 2145916800 * (1 | vIntYear >> 31);
    }else{
        integer vIntMnth = ~-llList2Integer( vLstStp, 1 );
        vIntRtn = 86400 * ((integer)(vIntYear * 365.25 + 0.25) - 24837 +
          vIntMnth * 30 + (vIntMnth - (vIntMnth < 7) >> 1) + (vIntMnth < 2) -
          (((vIntYear + 2) & 3) > 0) * (vIntMnth > 1) +
          (~-llList2Integer( vLstStp, 2 )) ) +
          llList2Integer( vLstStp, 3 ) * 3600 +
          llList2Integer( vLstStp, 4 ) * 60 +
          llList2Integer( vLstStp, 5 );
    }
    return vIntRtn;
}

// returns the time in hours as a a string between now and "timeBeingChecked"
string hoursUntil(string timeBeingChecked) {
    // llGetTimeStamp returns current UTC time in form YYYY-MM-DDThh:mm:ss.ff..fZ

    list ltbcheck = llParseString2List(timeBeingChecked, ["-", "T", ":", "Z"], []);
    integer iTBcheckSeconds = uStamp2UnixInt(ltbcheck);
    integer iCurrentSeconds = llGetUnixTime();
    integer iSecondsUntil = iTBcheckSeconds - iCurrentSeconds;
    float fMinutesUntil = iSecondsUntil/60;
    float fHoursUntil = fMinutesUntil/60;
    integer iMinutesOver = (integer) fMinutesUntil % 60;
    
    return (string)((integer)fHoursUntil) + " hours and " + (string)((integer)iMinutesOver) + " minutes";
}


EmailEncryptedCodesToOfficials() {
    // send message to the email sub-scripts that its time to email the voter receipt codes only to the officials
    // the codes are sent together with a secret phrase only each official knows their own
    // together with the uuid of the voting machine and the location of the voting machine.
    llMessageLinked(LINK_THIS, 1, llList2String(official_pass,0), (string)llList2String(officials,2));
    llMessageLinked(LINK_THIS, 2, llList2String(official_pass,1), (string)llList2String(officials,6));
    llMessageLinked(LINK_THIS, 3, llList2String(official_pass,2), (string)llList2String(officials,10));
    llMessageLinked(LINK_THIS, 4, llList2String(official_pass,3), (string)llList2String(officials,14));
    llMessageLinked(LINK_THIS, 5, llList2String(official_pass,4), (string)llList2String(officials,18));
}


//*****************************************************
// Checking Functions
// Used by machine Owner to check machine state, voter eligibility listing
string vGetState(){
    // prints to Chat the state the machine is curently in.
    // voting machine owner type  /999666 state
    // where <owner channel> is the number given you on starting the machine    
    if (machine_status==DEFAULT) return "Machine is in DEFAULT";
    if (machine_status==VOTING_PENDING) return "Machine is in VOTE PENDING";
    if (machine_status==VOTING_OPEN)  return "Machine is in VOTING OPEN";
    if (machine_status==VOTING) return "Machine is in VOTING";
    if (machine_status==VOTING_CLOSED) return "Machine is in VOTING CLOSED";
    if (machine_status==READING) return "Machine is in READING CARDS";
    if (machine_status==ERROR)  return "Machine is in ERROR state";
    return "";
}

vPrintEligibileVoters(){
    // prints out to chat a list of all eligible voters
    // voting machine owener type /999666 eligible  
    // where <owner channel> is the number given you on starting the machine
    for (index=0; index<number_of_eligible_voters; index++){
        llSay(0, llList2String(voter_names, index*2) + " " + llList2String(voter_names, index*2+1));
    }
}

doOwnerCommand(string message){
    if (message=="state") {
        llOwnerSay(vGetState());
    } else if (message=="eligible") {
        vPrintEligibileVoters();
    } else if (message=="freemem") {
        llOwnerSay((string)llGetFreeMemory()); 
    } else if (message=="reset") {
        llResetScript();
    } else if (llGetSubString(llStringTrim(message,STRING_TRIM),0,3)=="add ") {
        // to add a missed eligible voter, type /999666 add <New Name>, where <New Name> is the avatar legacy name First Last format
        string newName = llToUpper(llGetSubString(llStringTrim(message,STRING_TRIM),4,-1));
        voter_names = voter_names + [newName, 0];
        ++number_of_eligible_voters;    
       
        llSay(PUBLIC_CHANNEL,"voter " + newName + " was added to eligible votes list");   
    }
}

// **********************************************************
// default state entered when the machine is rezzed, it launches the voting pending state
default {
    state_entry() {
        // assign random control channel for the machine owner to debug and check eligile voters
        
        llOwnerSay("Your Command Channel is 999666");
        
        machine_status = DEFAULT;
        machine_state = "Inititialising";
        llSetText(machine_state,<0,0,1>,1.0);
        llSay(PUBLIC_CHANNEL, llGetScriptName() + " Voting Machine UUID Key: - " + (string) llGetKey());
        llSay(PUBLIC_CHANNEL, llGetScriptName() + " version " + version);
        llSay(PUBLIC_CHANNEL, llGetScriptName() + " Free Memory " + (string)llGetFreeMemory());
        // send message to the vy.z_emailX scripts for them to clear their memories.
        llMessageLinked(LINK_THIS, RESET, "", "");
        
        state voting_pending;
    }
        
}

// *************************************************************
// voting pending state, this is BEFORE the voting start time, and is responsible for reading the vote configuration
// and allowing members to check if they are eligible voters.
state voting_pending {
    state_entry() {
        machine_status = VOTING_PENDING;
        machine_state = "INITIALISING\n";
        llSetText(machine_state,<1,1,0>,1.0);
        hOwnerListen = llListen(owner_channel,"", llGetOwner(),"");

        if (got_Config==FALSE) {
            llMessageLinked(LINK_THIS, RESET, "", "");
            state read_config;
        } else if(got_Config==ERROR) {
            machine_state += "ERROR in CONFIG notecard\n";
        } else {
            machine_state += "CONFIG is OK\n";
        }
        llSetText(machine_state,<1,1,0>,1.0);

        if (got_Officials==FALSE) {
            cardname="officials";
            state read_list;
        } else if(got_Officials==ERROR) {
            machine_state += "ERROR in OFFICIALS notecard\n";
        } else {
            machine_state += "OFFICIALS card is OK\n";
        }
        llSetText(machine_state,<1,1,0>,1.0);

        if (got_Voters==FALSE) {
            cardname="voters";
            state read_list;
        } else if(got_Voters==ERROR) {
                machine_state += "ERROR in VOTERS notecard\n";
        } else {
                machine_state += "VOTERS card is OK\n";
        }
        llSetText(machine_state,<1,1,0>,1.0);

        if (got_Choices==FALSE) {
            cardname="choices";
            state read_list;
        } else if(got_Choices==ERROR) {
                machine_state += "ERROR in CHOICES notecard\n";
        } else {
                machine_state += "CHOICES card is OK\n";
        }
        llSetText(machine_state,<1,1,0>,1.0);

        if (got_Authentication_Card==FALSE) {
            state read_authentication_cards;
        } else if(got_Authentication_Card==ERROR) {
                machine_state += "ERROR in AUTHENTICATION PASS cards\n";
        } else {
                machine_state += "AUTHENTICATION PASS cards are OK\nWAITING FOR VOTING TO OPEN";
        }
        
        llSetText(machine_state,<1,1,0>,1.0);

        // check if all notecard items are ok.
        if (got_Config==ERROR || got_Officials==ERROR || got_Voters==ERROR || got_Choices==ERROR || got_Authentication_Card==ERROR) {
            // something needs fixing
            // SET hover text to RED and IM the vote machine owner (Surf Rang) to come the correct problem.
            llSetText(machine_state,<1,0,0>,1.0);
            llInstantMessage(llGetOwner(),"Please check all the notecards in the voting machine, there is a problem.");
        } else {
            // all notecards are ok .. check each minute for vote opening time
            if (timeHasPassed(voting_start_time) && !timeHasPassed(voting_end_time)) {
                llSetTimerEvent(TIMER_OFF);
                
                state voting_open;
            }
            llSetTimerEvent(ONE_MINUTE);
        }
    }

    timer() {
        // if current time is greater than or equal to vote start time (and we havent passed the end time either), open the voting
        if (timeHasPassed(voting_start_time) && !timeHasPassed(voting_end_time)) {
            llSetTimerEvent(TIMER_OFF);
            //llMessageLinked(LINK_THIS, RESET, "", "");
            state voting_open;
        } else {
            llSay(PUBLIC_CHANNEL, "Time until Voting Opens " + hoursUntil(voting_start_time));
        }
    }

    // wait for inventory change events and re-read cards if necessary
    changed(integer change) {
        if (change & CHANGED_INVENTORY) {
            // re-enter and re-read cards if inventory is changed prior to voting open
            llResetScript();
        }
    }

    // voter click at this stage (after initialization) .. message that voting has not yet opened
    // plus confirm that that person either is or is not on the list
    touch_end(integer num) {
        if ( got_Voters ) {
            voter_key = llDetectedKey(0);
            voter_name= llToUpper(llDetectedName(0));
            llInstantMessage(voter_key,"Voting is not yet open.");
            if (CheckVoterEligibility(voter_name)) {
                llInstantMessage(voter_key,"You are Eligible for Voting when voting opens");
            } else {
                llInstantMessage(voter_key,"You are Not Eligible for Voting when voting begins. If you believe this is an error contact the Election officials");
            }
        }
    }
    
    listen(integer channel, string name, key id, string message){
        doOwnerCommand(message);
    }
    
    state_exit () {
        llSetTimerEvent(TIMER_OFF);       
    }    
}

// /**************************************************************
// voting open state, waits for an eligible voter to click the machine then switches to voting state
state voting_open {
    state_entry() {
        
        machine_status = VOTING_OPEN;
        machine_state = "VOTING IS OPEN\n Voters so far (" + (string)voter_count + ")" ;
        
        llSetText(machine_state,<0,1,0>,1.0);
        hOwnerListen = llListen(owner_channel,"", llGetOwner(),"");
        
        llSetTimerEvent(ONE_MINUTE);
        // keep an eye on script free memory
        if (DEBUG) llOwnerSay((string)llGetFreeMemory());
    }

    // wait for a voter touch
    touch_end(integer num) {
        llSetTimerEvent(TIMER_OFF);
        voter_key = llDetectedKey(0);
        
        voter_name= llToUpper(llStringTrim(llDetectedName(0),STRING_TRIM));   // <<< uncomment this *********
        
        // test rig to step through all eleigbile voters on click  <<< comment all this block out for operational script 
        // ***********************************************************
        // voter_index = voter_index + 1;
        // if (voter_index < number_of_eligible_voters){
        //     voter_name = llList2String(voter_names, voter_index * 2);
        // } else {
        //     llOwnerSay ("everyone has voted.");
        // }
        // ***********************************************************
    
    
        if (DEBUG) llOwnerSay(voter_name);
        
        voter_click_position = llDetectedTouchST(0);
        // check if voter is on the list
        if (CheckVoterEligibility(voter_name)) {
            // and if they havent already voted. enter the voting state
            if (CheckVoterNotVoted(voter_name)) {
                state voting;
            } else {
                llInstantMessage(voter_key,"You have already voted. You may only vote once.");
            }
        } else {
            llInstantMessage(voter_key,"Sorry you are not an eligible voter. Contact an election official for an explanation of eligibilty");
        }
        llSetTimerEvent(ONE_MINUTE);
    }

    timer() {
        // if current time is greater than or equal to voting close time .. close voting
        if (timeHasPassed(voting_end_time)) {
            state voting_closed;
        } else{
            llSay(PUBLIC_CHANNEL, "Time until Voting closes " + hoursUntil(voting_end_time));
        }
    }
    
    listen(integer channel, string name, key id, string message) {
        doOwnerCommand(message);
    }
    
    state_exit () {
        llSetTimerEvent(TIMER_OFF);        
    }
}

// /**************************************************************
// voting state, this switches the machine to only listen to the current voter (no touch events active)
// it accepts their votes until they have voted for all open positions, it then confirms, locks in the vote and gives the voter there Voter code
state voting {
    state_entry() {

        hOwnerListen = llListen(owner_channel,"", llGetOwner(),"");
        
        // vote list temporarily holds the positions of the selected candidates within in the choice list
        vote=[];
        // votenames temporarilyholds the names of the selected candidates
        votenames=[];

        voter_record_position=-1;


        // randomize the list of candidate choices to neutralize "donkey vote" effect
        randomizedMenu = llListRandomize(choices,1);
        voting_menu = ["start again", "give scripts", "cancel"] + randomizedMenu;

        // listen on a different random channel for each voter
        menu_channel = (integer) llFrand(9999999) * -1;
        hMenuListen = llListen(menu_channel, "", voter_key,"");
        machine_status = VOTING;
        machine_state = "VOTING MACHINE IN USE\n" + voter_name;
        llSetText(machine_state,<1,0,0>,1.0);

        llSetTimerEvent(TWO_MINUTES);
        llInstantMessage(voter_key, vote_title + "You have 2 minutes to make & confirm your selections. If you crash or do not complete your vote in 2 minutes you can try again later");
        llDialog(voter_key, vote_title +", please make your " + (string)number_of_positions + " selection(s)", voting_menu, menu_channel);
        // number of selections the voter has made
        vote_count =0;

        if (DEBUG) llOwnerSay((string)llGetFreeMemory());
    }

    listen(integer channel, string name, key id, string menu_selection) {
        // we got a menu selection from our voter lets handle it
        if (channel==menu_channel && (id==voter_key)) {
            for (index=0; index < number_of_choices; ++index) {
                // look for the candidates name in the list of choices
                if (llToUpper(menu_selection)==llToUpper(llList2String(choices,index))) {
                    // hold Choice Index Number(s) in temp local list[vote] until confirmed by voter
                    vote = [index] + vote;
                    votenames = [llList2String(choices,index)] + votenames;
                    vote_count += 1;
                    // remove the name just selected from the list of choices so there can be only one vote per candidate
                    for (menu_position=0; menu_position<number_of_choices; ++menu_position) {
                        if (llToUpper(menu_selection)==llToUpper(llList2String(randomizedMenu,menu_position))) {
                            randomizedMenu = llDeleteSubList(randomizedMenu, menu_position, menu_position);
                            voting_menu = ["start again", "give scripts", "cancel"] + randomizedMenu;
                        }
                    }
                }
            }
            if (menu_selection=="start again") {
                vote_count = 0;
                votenames=[];
                vote=[];
                randomizedMenu = llListRandomize(choices,1);
                voting_menu = ["start again", "give scripts", "cancel"] + randomizedMenu;
            }
            if (menu_selection=="cancel") {
                llInstantMessage(voter_key,"You selected Cancel, Your vote has NOT been recorded, you may try again later.");
                state voting_open;
            }
            if (menu_selection=="give scripts") {

                llGiveInventoryList(voter_key, llGetObjectName(), [llGetScriptName(), "VotingMachineMemory2.0", "config", "officials", "voters", "choices"]);
                // subscripts used to email code lists (and code lists only -  no votes) to officials
            }
            
            if (menu_selection=="confirmed") {
                llSetTimerEvent(TIMER_OFF);
                integer votersChoice=0;
                integer choiceCurrentTally=0;
                // add to the tally of this voters selected candidates choices
                for (index=0; index < number_of_positions; ++index) {
                    // get the choice candidate-index-number of each choice the voter made, from the temporary [vote] list
                    votersChoice = llList2Integer(vote, index);
                    // retrieve the current tally for that candidate choice
                    choiceCurrentTally = llList2Integer(tally, votersChoice);
                    // add 1 to the tally for that choice
                    choiceCurrentTally += 1;
                    // update the tally list with the new total for that choice
                    tally = llListReplaceList(tally,[choiceCurrentTally],votersChoice,votersChoice);
                    // send updated tally to VotingMemoryScript
                    llMessageLinked(LINK_THIS, MTALLY, llList2CSV(tally), "");
                    // get the next vote for this voter ...and repeat
                    if (DEBUG) llOwnerSay("index: " + (string)index + " VotersChoice: " + (string)votersChoice + " choiceCurrentTally: " + (string)choiceCurrentTally + " Tally\n" + llDumpList2String(tally,"\n"));
                }
                // then,  add one to the total number of voters that have voted.
                ++voter_count;
                // calculate unique non-decryptable voting code
                voter_encryption_code = llSHA1String((string)voter_key + (string)llGetKey() + (string)voter_click_position );
                // create a temp list consisting the vote encryption code and the names of the choices the voter selected
                voter_encryption_code_vote = [voter_encryption_code] + votenames;
                // append this encoded vote to the vote list.
                llMessageLinked(LINK_THIS, MVEC, llDumpList2String( voter_encryption_code_vote, "~"),"");
                // *voter_encryption_code_vote_list = voter_encryption_code_vote_list + voter_encryption_code_vote;

                // update "Has Voted" for this voter .
                //  find the position of this voters name in the voter_names list
                voter_record_position = llListFindList(voter_names, (list)voter_name);
                //  replace the record for this voter with [voter_name, 1]  (where 1 designates  "has voted") (the 2 at the end is the length of the "stride" (ie 2 fields per voter record))
                voter_names = llListReplaceList(( voter_names=[] ) + voter_names, [voter_name, 1], voter_record_position, voter_record_position+1);
                //
                // private message the voter with their voter code and vote.
                string voter_message="";
                
                //llInstantMessage(voter_key, voter_name + " thank you for voting!");
                //llInstantMessage(voter_key,"Your voting confirmation code is: " + voter_encryption_code);
                //llInstantMessage(voter_key,"You voted for:\n" + llDumpList2String(votenames, "\n"));
                //llInstantMessage(voter_key,"Please copy your voting confirmation code to your own notecard for verification");
                voter_message = "\nThis is a PRIVATE IM to YOU\n";
                voter_message +=  voter_name + " thank you for voting!\n";
                voter_message += "Your voting confirmation code is: " + voter_encryption_code + "\n";
                voter_message += "You voted for:\n" + llDumpList2String(votenames, "\n");
                voter_message += "\nPlease copy your voting confirmation code to your own notecard for verification";
                                
                llInstantMessage(voter_key, voter_message);
                // send the code (AND ONLY THE CODE) to the email sub-scripts
                // they will store the codes in a list ready to email only the codes to the officials.
                llMessageLinked(LINK_THIS, 0, voter_encryption_code, "");

                // return to voting open state so next voter can vote.
                state voting_open;
            }
            if (vote_count == number_of_positions) {
                // confirm vote
                // dialog, confirmed or start again?
                llDialog(voter_key, voter_name +" please confirm your " + (string) vote_count +  " selections, you have selected;\n" + llDumpList2String(votenames, "\n"), ["confirmed", "start again", "cancel"], menu_channel);
            } else {
                    // pop the choices menu again until they have voted for each open position.
                    llDialog(voter_key, "please make your selections (so far " + (string) vote_count + " of " + (string)number_of_positions + ")", voting_menu, menu_channel);
            }
        } else if (channel==owner_channel) {
            doOwnerCommand(menu_selection);
        }
    }

    timer () {
        // voter has not completed their vote in 2 minutes; cancel the vote (they can try again) and return to voting_open
        // this is to prevent the machine being locked in this state and blocking others from voting if for exampel the voter crashes
        // during their vote, voter is sent an instant message.
        llInstantMessage(voter_key,"Sorry " + voter_name + " you did not make your selections within two minutes. Your vote has NOT been recorded you may try again.");
        state voting_open;
    }
    
    state_exit () {
        llSetTimerEvent(TIMER_OFF);
    }
}

// ***************************************************************
// voting closed, basically just provides the final tallies
state voting_closed {
    // set float text to White and "VOTING IS CLOSED"
    // present menu
    // give total number of voters
    // give tally for each candidate
    // if a voter .. give option to decode and read back their own vote and vote time
    state_entry() {
        machine_status = VOTING_CLOSED;
        machine_state = "VOTING IS CLOSED";
        llSetText(machine_state,<1,1,1>,1.0);
        hOwnerListen = llListen(owner_channel,"", llGetOwner(),"");
        
        // sort the voter code list to avoid vote time correlation sequencing
        // *voter_encryption_code_vote_list = llListSort(voter_encryption_code_vote_list, 1 + number_of_positions, TRUE);
        // send message to VotingStoreScript that Voting has closed ... trigger a sort and an output of results
        llMessageLinked(LINK_THIS, MEND, "","");
        // send vote codes (without vote code seelctions) to officials to ensure the final printout is authentic.
        // each official also receives their personal code phrase ensuring that the records only come formt he official machine.
        llSleep(10.0);
        EmailEncryptedCodesToOfficials();
        // outputResults(TRUE);
        llSleep(10.0);
        llMessageLinked(LINK_THIS, MOUTPUT, "","");
    }

    touch_end(integer num) {
        // printout out the code list and choice tallies
        llMessageLinked(LINK_THIS, MOUTPUT, "","");
        // outputResults(FALSE);
    }
    
    listen(integer channel, string name, key id, string message){
        doOwnerCommand(message);
    }    
}

// /***************************************************************
// all the note-card reading states

state read_list {
    // read candidate choices card
    state_entry() {
        // cardname has been set back in state voting_pending prior to calling this state
        linecount = 0;    
        // set the clock for 2 minutes .. longer than that and something screwed up reading the card
        llSay(PUBLIC_CHANNEL,cardname + "\n");
        llSetTimerEvent(TWO_MINUTES);
        dataQueryID = llGetNotecardLine(cardname, linecount);
    }

    dataserver(key query_id, string notecardline) {
        if (query_id == dataQueryID) {
            if (notecardline != EOF) {
                if (llStringLength(notecardline)>1) {
                    if (cardname=="choices") {
                        choices = choices + [llGetSubString(notecardline,0,23)];
                        ++number_of_choices;
                        llMessageLinked(LINK_THIS, MCHOICES, notecardline, "");
                    } else if (cardname=="voters"){
                        voter_names = voter_names + [llToUpper(llStringTrim(notecardline,STRING_TRIM)), 0];
                        ++number_of_eligible_voters;
                    } else if (cardname=="officials"){
                        // add official to the strided list[ first name, last name, email, uuid]
                        officials = officials + llParseString2List(notecardline,[" "],[]);
                        ++number_of_officials;                    
                    }
                }
                // request next line from notecard
                llSay(PUBLIC_CHANNEL, llToUpper(notecardline));
                ++linecount;
                dataQueryID = llGetNotecardLine(cardname, linecount);
            } else {
                if (cardname=="choices") {
                    got_Choices = TRUE;
                } else if (cardname=="voters"){
                    got_Voters = TRUE;
                } else if (cardname=="officials"){
                    got_Officials = TRUE;
                }
                state voting_pending;
            }
        }
    }

    timer(){
        // it has taken too long and no data_server or http_response was received
        llOwnerSay("read " + cardname + " card timed out afer two minutes");
        if (cardname=="choices") {
            got_Choices = ERROR;
        } else if (cardname=="voters"){
            got_Voters = ERROR;
        } else if (cardname=="officials"){
            got_Officials = ERROR;
        }
        state voting_pending;
    }
    
    state_exit(){
        llSetTimerEvent(TIMER_OFF);        
    }
}


state read_config {
    // looks for 'config' notecard should be formatted as follows:
    // start YYYY-MM-DDThh:mm:ss     // eg 2010-06-01T08:00:00   in UTC
    // end   YYYY-MM-DDThh:mm:ss     // eg 2010-06-02T07:59:59   in UTC
    // title "SLSA election June 1st 2010"    //nb include the " quote marks" keep title on a single line
    // help "Contact the election panel for instructions or see the post in the SLSA forum http:/// for detailed instructions."
    // positions X                    // eg positions 3
    state_entry() {
        got_Config = FALSE;
        cardname = "config";

        llSetTimerEvent(TWO_MINUTES);
        // request first line of config notecard
        linecount = 0;
        dataQueryID = llGetNotecardLine(cardname, linecount);
    }

    dataserver(key query_id, string notecard_line) {
        if (query_id == dataQueryID) {
            if (notecard_line != EOF) {
                llSay(PUBLIC_CHANNEL, (string)linecount + ": " + notecard_line);

                if (llToUpper(llGetSubString(notecard_line,0,4))=="START") {
                    voting_start_time = llStringTrim(llGetSubString(notecard_line, 5, -1),STRING_TRIM);
                    vst_elements = llParseString2List(voting_start_time, ["-",":"], ["T"]);
                    gotStart=TRUE;
                    // check year, month and day to ensure they are valid numbers
                    for (index=0; index<2; index++) {
                        if (llList2Integer(vst_elements, index)==0) {
                            gotStart=FALSE;
                            llSay(PUBLIC_CHANNEL, "Config Error: Start Date Invalid Format.");
                        }
                    }
                }
                if (llToUpper(llGetSubString(notecard_line,0,2))=="END") {
                    voting_end_time = llStringTrim(llGetSubString(notecard_line, 3, -1),STRING_TRIM);
                    vet_elements = llParseString2List(voting_start_time, ["-",":"], ["T"]);
                    gotEnd = TRUE;
                    // check year, month and day to ensure they are valid numbers
                    for (index=0; index<2; index++){
                        if (llList2Integer(vet_elements,index)==0) {
                            gotEnd=FALSE;
                            llSay(PUBLIC_CHANNEL, "Config Error: End Date Invalid Format.");
                        }
                    }
                }
                if (llToUpper(llGetSubString(notecard_line,0,4))=="TITLE") {
                    vote_title = llStringTrim(llGetSubString(notecard_line, 5, -1),STRING_TRIM);
                    gotTitle = TRUE;
                }
                if (llToUpper(llGetSubString(notecard_line,0,3))=="HELP") {
                    help_text = llStringTrim(llGetSubString(notecard_line, 4, -1),STRING_TRIM);
                    gotHelp = TRUE;
                }
                if (llToUpper(llGetSubString(notecard_line,0,8))=="POSITIONS") {
                    number_of_positions = (integer) llStringTrim(llGetSubString(notecard_line, 9, -1),STRING_TRIM);
                    llMessageLinked(LINK_THIS, MPOSITIONS, (string) number_of_positions, "");
                    gotPositions = TRUE;
                    if (number_of_positions < 1 || number_of_positions > 5) {
                        gotPositions = FALSE;
                        llSay(PUBLIC_CHANNEL, "Config Error: Number of positions must be between 1 and 5 inclusive.");
                    }
                }

                // request next line
                ++linecount;
                dataQueryID = llGetNotecardLine(cardname, linecount);
            } else {
                if (gotStart && gotEnd && gotTitle && gotHelp && gotPositions) {
                    got_Config = TRUE;
                } else {
                    got_Config = ERROR;
                }
                state voting_pending;
            }
        }
    }

    timer() {
        // it has taken too long and no data server response was received
        llOwnerSay("read config card timed out afer two minutes");
        got_Config = ERROR;
        state voting_pending;
    }
    
    state_exit () {
        llSetTimerEvent(TIMER_OFF);    
    }
}

state read_authentication_cards {
    // each official drops a notecard into the machine with their own secret phrase.
    // the card cannot be copied or viewwd by anyone
    // the card name should begin with "pass"
    // the phrase will be sent to the official along with the VEC, Machine UUID and Location
    // ensuring that they are receiving codes only from the official machine which they dropped their card into
    state_entry() {
        got_Authentication_Card = FALSE;
        // number of notecards in the voting machine
        integer num_cards=0;
        // number of "pass_" notecard in the voting machine
        pass_cards=0;
        // number of notecards whose phrase has been read in
        pass_cards_read=0;
        cardname="";
        official_pass=["","","","",""];
        query_ids=[];
        // temp list to give the record number of the official who is the creator of the pass card
        integer official_position = -1;
        // first item in the official's position list, giving the record number
        integer official_record = 0;
        // list to hold [notecard read request ids, and official's record number] pairs
        string creator_key;


        num_cards = llGetInventoryNumber(INVENTORY_NOTECARD);
        for (index=0; index<num_cards; ++index) {
            cardname = llGetInventoryName(INVENTORY_NOTECARD, index);
            if (llToUpper(llGetSubString(cardname, 0, 3))=="PASS") {
                ++pass_cards;
                creator_key = (string) llGetInventoryCreator(cardname);
                // find creator key in officials
                official_position = llListFindList(officials, [creator_key]);
                official_record = (official_position + 1) / 4 ;
                if (official_record == 0) {
                    llOwnerSay("pass card " + (string) index + " : creator key " + (string) creator_key + " not found in list of officials' keys ");
                    got_Authentication_Card = ERROR;
                    llSetTimerEvent(TIMER_OFF);
                    state voting_pending;
                } else {
                    // read the card and save the phrase in the correlated list
                    // query_ids is a list of query_ids generated to read the first line of each pass card, 
                    // together with the position of this official in the list of officials
                    query_ids = [llGetNotecardLine(cardname, 0), official_record] + query_ids;
                }
            }
        }
        // check in two minutes to see if all cards are read.
        llSetTimerEvent(TWO_MINUTES);
    }

    dataserver(key query_id, string secret_phrase) {
        // look in the list of notecard-first-line query_ids, for this returned correlated query_id
        position = llListFindList(query_ids, [query_id]);
        // a matching query_id was found in the list of query id's for each officials' pass card
        if (position != -1) {
            if (secret_phrase != EOF) {
                // up the count of pass notecards that have returned their phrase
                ++pass_cards_read;
                // place the "secret phrase" for this official into the right spot (their spot) in the list of phrases
                // (query_ids, position+1) is a list item that holds the "officials'" position, within the list of officials)
                official_pass = llListReplaceList(official_pass, [secret_phrase], llList2Integer(query_ids,position+1)-1, llList2Integer(query_ids,position+1)-1);
                if (pass_cards == number_of_officials && pass_cards_read == number_of_officials) {
                    got_Authentication_Card = TRUE;

                    state voting_pending;
                }
            } else { // EOF no phrase was returned for a card
                got_Authentication_Card = ERROR;
                llSay(PUBLIC_CHANNEL, "Config Error: One pass card has no phrase in it");
                state voting_pending;
            }
        }
    }

    timer() {
        // it has taken too long and no data_server event was received, or not enough was, so the pass cards are "incorrect"
        llOwnerSay("read authentication cards timed out afer two minutes");
        got_Authentication_Card = ERROR;
        state voting_pending;
    }
    
    state_exit () {
        llSetTimerEvent(TIMER_OFF);    
    }
}