Copyright (C) IBM Corp. 2006
Author(s): Charles P. Wright (cpwright@us.ibm.com)

/*
 *  This program 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 program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Authors : Benjamin GAUTHIER - 24 Mar 2004
 *            Joseph BANINO
 *            Olivier JACQUES
 *            Richard GAYRAUD
 *            Marc LAMBERTON
 *            Herve PELLAN
 *            David MANSUTTI
 *            Francois-Xavier Kowalski
 *            Gerard Lyonnaz
 *            From Hewlett Packard Company.
 *            Guillaume Teissier from FTR&D
 *            Shriram Natarajan
 *            Peter Higginson
 *            Venkatesh
 *            Lee Ballard
 *            Wolfgang Beck
 *            Marc Van Diest from Belgacom
 *	      Charles P. Wright from IBM Research
 *            Eric Miller
 *            Enrico Hartung
 *            Nasir Khan
 *            Vlad Troyanker
 *            Amit On from Followap
 *            Jan Andres from Freenet
 *            Ben Evans from Open Cloud
 *            Marc Van Diest from Belgacom
 *            F. Tarek Rogers
 *            Vincent Luba
 *            Clement Chen
 */
Index: trunk/actions.cpp
===================================================================
--- trunk.orig/actions.cpp
+++ trunk/actions.cpp
@@ -91,18 +91,18 @@ void CAction::afficheInfo()
 {
   if (M_action == E_AT_ASSIGN_FROM_REGEXP) {
     if(M_lookingPlace == E_LP_MSG) {
-      printf("Type[%d] - where[%s] - checkIt[%d] - varId[%d]",
+      printf("Type[%d] - where[%s] - checkIt[%d] - $%s",
              M_action,
              "Full Msg",
              M_checkIt,
-		       M_varId);
+		       variableRevMap[M_varId]);
     } else {
-      printf("Type[%d] - where[%s-%s] - checkIt[%d] - varId[%d]",
+      printf("Type[%d] - where[%s-%s] - checkIt[%d] - $%d",
              M_action,
              "Header",
              M_lookingChar,
              M_checkIt,
-		       M_varId);
+		       variableRevMap[M_varId]);
     }
   } else if (M_action == E_AT_EXECUTE_CMD) {
     if (M_cmdLine) {
@@ -115,21 +115,21 @@ void CAction::afficheInfo()
   } else if (M_action == E_AT_ASSIGN_FROM_SAMPLE) {
       char tmp[40];
       M_distribution->textDescr(tmp, sizeof(tmp));
-      printf("Type[%d] - sample varId[%d] %s", M_action, M_varId, tmp);
+      printf("Type[%d] - sample varId[%d] %s", M_action, variableRevMap[M_varId], tmp);
   } else if (M_action == E_AT_ASSIGN_FROM_VALUE) {
-      printf("Type[%d] - assign varId[%d] %lf", M_action, M_varId, M_doubleValue);
+      printf("Type[%d] - assign varId[%d] %lf", M_action, variableRevMap[M_varId], M_doubleValue);
   } else if (M_action == E_AT_ASSIGN_FROM_STRING) {
-      printf("Type[%d] - string assign varId[%d] [%-32.32s]", M_action, M_varId, M_message);
+      printf("Type[%d] - string assign varId[%d] [%-32.32s]", M_action, variableRevMap[M_varId], M_message);
   } else if (M_action == E_AT_VAR_ADD) {
-      printf("Type[%d] - add varId[%d] %lf", M_action, M_varId, M_doubleValue);
+      printf("Type[%d] - add varId[%d] %lf", M_action, variableRevMap[M_varId], M_doubleValue);
   } else if (M_action == E_AT_VAR_MULTIPLY) {
-      printf("Type[%d] - multiply varId[%d] %lf", M_action, M_varId, M_doubleValue);
+      printf("Type[%d] - multiply varId[%d] %lf", M_action, variableRevMap[M_varId], M_doubleValue);
   } else if (M_action == E_AT_VAR_DIVIDE) {
-      printf("Type[%d] - divide varId[%d] %lf", M_action, M_varId, M_doubleValue);
+      printf("Type[%d] - divide varId[%d] %lf", M_action, variableRevMap[M_varId], M_doubleValue);
   } else if (M_action == E_AT_VAR_TEST) {
-      printf("Type[%d] - divide varId[%d] varInId[%d] %s %lf", M_action, M_varId, M_varInId, comparatorToString(M_comp), M_doubleValue);
+      printf("Type[%d] - divide varId[%d] varInId[%d] %s %lf", M_action, variableRevMap[M_varId], variableRevMap[M_varInId], comparatorToString(M_comp), M_doubleValue);
   } else if (M_action == E_AT_VAR_TO_DOUBLE) {
-      printf("Type[%d] - toDouble varId[%d]", M_action, M_varId);
+      printf("Type[%d] - toDouble varId[%d]", M_action, variableRevMap[M_varId]);
 #ifdef PCAPPLAY
   } else if ((M_action == E_AT_PLAY_PCAP_AUDIO) || (M_action == E_AT_PLAY_PCAP_VIDEO)) {
       printf("Type[%d] - file[%s]", M_action, M_pcapArgs->file);
Index: trunk/call.cpp
===================================================================
--- trunk.orig/call.cpp
+++ trunk/call.cpp
@@ -676,16 +676,12 @@ call::call(char * p_id, int userId, bool
 	M_callVariableTable = new CCallVariable *[maxVariableUsed + 1];
   }
   for(i=0; i<=maxVariableUsed; i++)
-    {
-      if (variableUsed[i]) {
-        M_callVariableTable[i] = new CCallVariable();
-        if (M_callVariableTable[i] == NULL) {
-          ERROR ("call variable allocation failed");
-        }
-      } else {
-        M_callVariableTable[i] = NULL;
-      }
+  {
+    M_callVariableTable[i] = new CCallVariable();
+    if (M_callVariableTable[i] == NULL) {
+      ERROR ("call variable allocation failed");
     }
+  }
 
   // If not updated by a message we use the start time 
   // information to compute rtd information
Index: trunk/message.cpp
===================================================================
--- trunk.orig/message.cpp
+++ trunk/message.cpp
@@ -206,7 +206,7 @@ SendingMessage::SendingMessage(char *src
 	  }
         } else if(*keyword == '$') {
 	  newcomp->type = E_Message_Variable;
-	  newcomp->varId = atoi(keyword + 1);
+	  newcomp->varId = get_var(keyword + 1, "Variable keyword");
 	} else if(!strncmp(keyword, "fill", strlen("fill"))) {
 	  newcomp->type = E_Message_Fill;
 	  char filltext[KEYWORD_SIZE];
@@ -219,7 +219,7 @@ SendingMessage::SendingMessage(char *src
 	  getKeywordParam(keyword, "variable=", varName);
 
 	  newcomp->literal = strdup(filltext);
-     newcomp->varId = get_long(varName, "Fill Variable");
+	  newcomp->varId = get_var(varName, "Fill Variable");
      } else if(!strncmp(keyword, "last_", strlen("last_"))) {
            if(!strncmp(keyword, "last_Request_URI", strlen("last_Request_URI"))){
               newcomp->type = E_Message_Last_Request_URI;
Index: trunk/scenario.cpp
===================================================================
--- trunk.orig/scenario.cpp
+++ trunk/scenario.cpp
@@ -138,8 +138,11 @@ message::~message()
 
 message*      scenario[SCEN_MAX_MESSAGES];
 CVariable***  scenVariableTable;
-bool	      *variableUsed;
 int	      maxVariableUsed = -1;
+typedef std::map<std::string, int> var_map;
+var_map	      variableMap;
+char	      **variableRevMap;
+int	      *variableReferences;
 int           scenario_len = 0;
 char          scenario_name[255];
 int           toolMode  = MODE_CLIENT;
@@ -300,34 +303,60 @@ double xp_get_bool(const char *name, con
   return xp_get_bool(name, what);
 }
 
-void xp_use_var(int var) {
-  if (var > maxVariableUsed) {
-    bool *tmpUsed = new bool[var + 1];
-    int i, j;
-    CVariable ***tmpScenVars = new CVariable **[var + 1];
-    for (i = 0; i <= maxVariableUsed; i++) {
-	tmpUsed[i] = variableUsed[i];
-	tmpScenVars[i] = new CVariable * [SCEN_MAX_MESSAGES];
-	for (j = 0; j < SCEN_MAX_MESSAGES; j++) {
-	  tmpScenVars[i][j] = scenVariableTable[i][j];
-	}
-	delete scenVariableTable[i];
+int get_var(const char *varName, const char *what) {
+  /* Check the name's validity. */
+  if (varName[0] == '\0') {
+    ERROR_P1("Variable names may not be empty for %s\n", what);
+  }
+  if (strcspn(varName, "$,") != strlen(varName)) {
+    ERROR_P1("Variable names may not contain $ or , for %s\n", what);
+  }
+
+  /* If this variable has already been used, then we have nothing to do. */
+  var_map::iterator var_it = variableMap.find(varName);
+  if (var_it != variableMap.end()) {
+    variableReferences[var_it->second]++;
+    return var_it->second;
+  }
+
+  /* Assign this variable the next slot. */
+  int varNum = maxVariableUsed > 0 ? maxVariableUsed + 1 : 1;
+
+  CVariable ***tmpScenVars = new CVariable **[varNum + 1];
+  char **tmpVariableRevMap = new char *[varNum + 1];
+  int *tmpVariableReferences = new int[varNum + 1];
+  int i;
+  for (i = 0; i <= maxVariableUsed; i++) {
+    tmpScenVars[i] = new CVariable * [SCEN_MAX_MESSAGES];
+    for (int j = 0; j < SCEN_MAX_MESSAGES; j++) {
+      tmpScenVars[i][j] = scenVariableTable[i][j];
     }
-    delete [] variableUsed;
+    delete scenVariableTable[i];
+    tmpVariableRevMap[i] = variableRevMap[i];
+    tmpVariableReferences[i] = variableReferences[i];
+  }
+  if (scenVariableTable) {
     delete [] scenVariableTable;
-    variableUsed = tmpUsed;
-    scenVariableTable = tmpScenVars;
-    for (;i <= var; i++) {
-	variableUsed[i] = false;
-	scenVariableTable[i] = new CVariable * [SCEN_MAX_MESSAGES];
-	for (j = 0; j < SCEN_MAX_MESSAGES; j++) {
-	  scenVariableTable[i][j] = NULL;
-	}
+    delete [] variableRevMap;
+    delete [] variableReferences;
+  }
+  scenVariableTable = tmpScenVars;
+  variableRevMap = tmpVariableRevMap;
+  variableReferences = tmpVariableReferences;
+
+  for (; i <= varNum; i++) {
+    scenVariableTable[i] = new CVariable * [SCEN_MAX_MESSAGES];
+    for (int j = 0; j < SCEN_MAX_MESSAGES; j++) {
+      scenVariableTable[i][j] = NULL;
     }
-
-    maxVariableUsed = var;
   }
-  variableUsed[var] = true;
+
+  variableMap[varName] = varNum;
+  variableRevMap[varNum] = strdup(varName);
+  variableReferences[varNum] = 1;
+  maxVariableUsed = varNum;
+
+  return varNum;
 }
 
 int xp_get_var(const char *name, const char *what) {
@@ -338,16 +367,7 @@ int xp_get_var(const char *name, const c
     ERROR_P2("%s is missing the required '%s' variable parameter.", what, name);
   }
 
-  helptext = (char *)malloc(100 + strlen(name) + strlen(what));
-  sprintf(helptext, "%s '%s' parameter", what, name);
-  int var = get_long(ptr, helptext);
-  free(helptext);
-
-  if(var < 0) {
-    ERROR("Variables must be positive integers!");
-  }
-  xp_use_var(var);
-  return var;
+  return get_var(ptr, what);
 }
 
 int xp_get_var(const char *name, const char *what, int defval) {
@@ -361,7 +381,6 @@ int xp_get_var(const char *name, const c
   return xp_get_var(name, what);
 }
 
-
 bool get_bool(const char *ptr, const char *what) {
   char *endptr;
   long ret;
@@ -490,6 +509,14 @@ void validate_rtds() {
   }
 }
 
+void validate_variable_usage() {
+  for (int i = 1; i <= maxVariableUsed; i++) {
+    if(variableReferences[i] == 1) {
+	ERROR_P1("Variable $%s is referenced only once!\n", variableRevMap[i]);
+    }
+  }
+}
+
 void init_rtds()
 {
   for (int i = 0; i < MAX_RTD_INFO_LENGTH; i++) {
@@ -932,23 +959,18 @@ void load_scenario(char * filename, int 
 
       if ( 0 != ( ptr = xp_get_value((char *)"next") ) ) {
         scenario[scenario_len] -> next = get_long(ptr, "next jump");
-         if ( 0 != ( ptr = xp_get_value((char *)"test") ) ) {
-           scenario[scenario_len] -> test = get_long(ptr, "test variable");
-         }
-         else {
-           scenario[scenario_len] -> test = -1;
-         }
-         if ( 0 != ( ptr = xp_get_value((char *)"chance") ) ) {
-           float chance = get_double(ptr,"chance");
-                                     /* probability of branch to next */
-           if (( chance < 0.0 ) || (chance > 1.0 )) {
-             ERROR_P1("Chance %s not in range [0..1]", ptr);
-           }
-           scenario[scenario_len] -> chance = (int)((1.0-chance)*RAND_MAX);
-         }
-         else {
-           scenario[scenario_len] -> chance = 0;/* always */
-         }
+	scenario[scenario_len] -> test = xp_get_var("test", "test variable", -1);
+	if ( 0 != ( ptr = xp_get_value((char *)"chance") ) ) {
+	  float chance = get_double(ptr,"chance");
+	  /* probability of branch to next */
+	  if (( chance < 0.0 ) || (chance > 1.0 )) {
+	    ERROR_P1("Chance %s not in range [0..1]", ptr);
+	  }
+	  scenario[scenario_len] -> chance = (int)((1.0-chance)*RAND_MAX);
+	}
+	else {
+	  scenario[scenario_len] -> chance = 0;/* always */
+	}
       } else {
         scenario[scenario_len] -> next = 0;
       }
@@ -971,6 +993,9 @@ void load_scenario(char * filename, int 
   if (scenario_len == 0) {
     ERROR("Did not find any messages inside of scenario!");
   }
+
+  /* Make sure that all variables are used more than once. */
+  validate_variable_usage();
 }
 
 CSample *parse_distribution(bool oldstyle = false) {
@@ -1211,8 +1236,8 @@ void getActionForThisMessage()
   char *        actionElem;
   char *        currentRegExp = NULL;
   char *        buffer = NULL;
-  unsigned int* currentTabVarId = NULL;
-  int           currentNbVarId;
+  char **       currentTabVarName = NULL;
+  int           currentNbVarNames;
   char * ptr;
   int           sub_currentNbVarId;
   
@@ -1298,52 +1323,47 @@ void getActionForThisMessage()
 	tmpAction->setCheckIt(false);
       }
       if (!(ptr = xp_get_value((char *) "assign_to"))) {
-         ERROR("assign_to value is missing");
-         }
+	ERROR("assign_to value is missing");
+      }
 
-      if(createIntegerTable(ptr, &currentTabVarId, &currentNbVarId) == 1) {
-   	xp_use_var(currentTabVarId[0]);
-	tmpAction->setVarId(currentTabVarId[0]);
-	/* and creating the associated variable */
-	if (scenVariableTable[currentTabVarId[0]][scenario_len] != NULL) {
-	  delete(scenVariableTable[currentTabVarId[0]][scenario_len]);
-	  scenVariableTable[currentTabVarId[0]][scenario_len] = NULL;
-	}
-	variableUsed[currentTabVarId[0]] = true;
-	scenVariableTable[currentTabVarId[0]][scenario_len] =
-	  new CVariable(currentRegExp);
-
-	if(!(scenVariableTable[currentTabVarId[0]][scenario_len]
-	      ->isRegExpWellFormed()))
-	  ERROR_P1("Regexp '%s' is not valid in xml "
-	      "scenario file", currentRegExp);
-
-	if (currentNbVarId > 1 ) {
-	  sub_currentNbVarId = currentNbVarId - 1 ;
-	  tmpAction->setNbSubVarId(sub_currentNbVarId);
-
-	  for(int i=1; i<= sub_currentNbVarId; i++) {
-	    xp_use_var(currentTabVarId[i]);
-
-	      tmpAction->setSubVarId(currentTabVarId[i]);
-
-	      /* and creating the associated variable */
-	      if (scenVariableTable[currentTabVarId[i]][scenario_len] != NULL) {
-		delete(scenVariableTable[currentTabVarId[i]][scenario_len]);
-		scenVariableTable[currentTabVarId[i]][scenario_len] = NULL;
-	      }
-	      scenVariableTable[currentTabVarId[i]][scenario_len] =
-		new CVariable(currentRegExp);
+      createStringTable(ptr, &currentTabVarName, &currentNbVarNames);
+
+      int varId = get_var(currentTabVarName[0], "assign_to");
+      tmpAction->setVarId(varId);
+      /* and creating the associated variable */
+      if (scenVariableTable[varId][scenario_len] != NULL) {
+	delete(scenVariableTable[varId][scenario_len]);
+	scenVariableTable[varId][scenario_len] = NULL;
+      }
+      scenVariableTable[varId][scenario_len] = new CVariable(currentRegExp);
 
-	      if(!(scenVariableTable[currentTabVarId[i]][scenario_len]
-		    ->isRegExpWellFormed()))
-		ERROR_P1("Regexp '%s' is not valid in xml "
-		    "scenario file", currentRegExp);
+      if(!(scenVariableTable[varId][scenario_len]->isRegExpWellFormed()))
+	ERROR_P1("Regexp '%s' is not valid in xml "
+	    "scenario file", currentRegExp);
+
+      if (currentNbVarNames > 1 ) {
+	sub_currentNbVarId = currentNbVarNames - 1 ;
+	tmpAction->setNbSubVarId(sub_currentNbVarId);
+
+	for(int i=1; i<= sub_currentNbVarId; i++) {
+	  int varId = get_var(currentTabVarName[i], "sub expression assign_to");
+
+	  tmpAction->setSubVarId(varId);
+
+	  /* and creating the associated variable */
+	  if (scenVariableTable[varId][scenario_len] != NULL) {
+	    delete(scenVariableTable[varId][scenario_len]);
+	    scenVariableTable[varId][scenario_len] = NULL;
 	  }
+	  scenVariableTable[varId][scenario_len] = new CVariable(currentRegExp);
+
+	  if(!(scenVariableTable[varId][scenario_len]->isRegExpWellFormed()))
+	    ERROR_P1("Regexp '%s' is not valid in xml "
+		"scenario file", currentRegExp);
 	}
+      }
 
-	delete[] currentTabVarId;
-      } 
+      freeStringTable(currentTabVarName, currentNbVarNames);
 
       if(currentRegExp != NULL) {
 	delete[] currentRegExp;
@@ -1574,6 +1594,40 @@ int createIntegerTable(char * P_listeStr
   }else return(0);
 }
 
+int createStringTable(char * inputString, char *** stringList, int *sizeOfList)
+{
+  if(!inputString) {
+    return 0;
+  }
+
+  *stringList = NULL;
+  *sizeOfList = 0;
+
+  do
+  {
+    char *p = strchr(inputString, ',');
+    if (p) {
+      *p++ = '\0';
+    }
+
+    *stringList = (char **)realloc(*stringList, sizeof(char *) * (*sizeOfList + 1));
+    (*stringList)[*sizeOfList] = strdup(inputString);
+    (*sizeOfList)++;
+
+    inputString = p;
+  }
+  while (inputString);
+
+  return 1;
+}
+
+void freeStringTable(char ** stringList, int sizeOfList) {
+  for (int i = 0; i < sizeOfList; i++) {
+    free(stringList[i]);
+  }
+  free(stringList);
+}
+
 /* These are the names of the scenarios, they must match the default_scenario table. */
 char *scenario_table[] = {
 	"uac",
Index: trunk/scenario.hpp
===================================================================
--- trunk.orig/scenario.hpp
+++ trunk/scenario.hpp
@@ -157,8 +157,9 @@ public:
 
 extern message   *   scenario[SCEN_MAX_MESSAGES];
 extern CVariable *** scenVariableTable;
-extern bool	     *variableUsed;
 extern int	     maxVariableUsed;
+/* This maps variable integers into their actual names. */
+extern char	     **variableRevMap;
 extern int	     scenario_len;
 extern char          scenario_name[255];
 extern int           toolMode;
@@ -186,6 +187,10 @@ int  createIntegerTable(char          * 
 int  isWellFormed(char * P_listeStr, 
                   int  * nombre);
 
+/* String table functions. */
+int createStringTable(char * inputString, char *** stringList, int *sizeOfList);
+void freeStringTable(char ** stringList, int sizeOfList);
+
 
 
 int find_scenario(const char *scenario);
@@ -198,6 +203,7 @@ long get_time(const char *ptr, const cha
 double get_double(const char *ptr, const char *what);
 bool get_bool(const char *ptr, const char *what);
 int time_string(double ms, char *res, int reslen);
+int get_var(const char *varName, const char *what);
 
 extern int get_cr_number(char *msg);
 
Index: trunk/sipp.cpp
===================================================================
--- trunk.orig/sipp.cpp
+++ trunk/sipp.cpp
@@ -894,7 +894,7 @@ void print_stats_in_file(FILE * f, int l
 	  desc[0] = '\0';
 	  scenario[index]->pause_distribution->timeDescr(desc, 23);
 	} else {
-	  snprintf(desc, 23, "$%d", scenario[index]->pause_variable);
+	  snprintf(desc, 23, "$%s", variableRevMap[scenario[index]->pause_variable]);
 	}
 	desc[24] = '\0';
 	scenario[index]->pause_desc = desc;
@@ -1148,7 +1148,6 @@ void print_variable_list()
   for(i=0; i<(scenario_len + 5 - j); i++) {
     printf(SIPP_ENDL);
   }
-  
 }
 
 /* Function to dump all available screens in a file */
