declare local var.cookie_decoded STRING;
declare local var.expires INTEGER;
declare local var.decision STRING;
declare local var.percentage INTEGER;
declare local var.string_to_sign STRING;
declare local var.sig STRING;
declare local var.user_id STRING;
declare local var.authed_user_id STRING;
declare local var.key_id STRING;
declare local var.seed INTEGER;
declare local var.duration_key STRING;
declare local var.duration INTEGER;
declare local var.logger_prefix STRING;

if (req.restarts == 0) {
  unset req.http.waitingroom_new_cookie;
}

# If you want to allocate waiting room spots based on
# user ID, set this to the user's verified ID.
set var.authed_user_id = client.ip;

// DEMO ONLY - REMOVE THIS LINE IN PRODUCTION
set var.authed_user_id = req.http.Fastly-Client-IP;

# Only enable the waiting room...
if (
  fastly.ff.visits_this_service == 0 && # on edge nodes
  req.restarts == 0 && # on first VCL pass
  table.lookup(solution_waitingroom_config, "enabled") == "true" # if configured to run
) {
  
  set var.logger_prefix = "syslog " + req.service_id + " " + table.lookup(solution_waitingroom_config, "logger_name") + " :: [WAITINGROOM] ";
  
  # Determine the percentage of requests to allow though
  set var.percentage = std.atoi(table.lookup(solution_waitingroom_config, "allow_percentage", "100"));
  if (var.percentage == 0 && table.lookup(solution_waitingroom_config, "allow_percentage") != "0") {
  	set var.percentage = 100;
  }
  
  # Special case for 'allow all'
  if (var.percentage >= 100) {
    set var.decision = "allow";
  
  # Special case for if user does not have a cookie
  } else if (!req.http.Cookie:waiting_room) {
    set var.decision = "anon";
    
  # Validate the cookie
  } else {

    set var.cookie_decoded = digest.base64_decode(req.http.Cookie:waiting_room);
    set var.expires = std.atoi(subfield(var.cookie_decoded, "exp", "&"));
    set var.sig = subfield(var.cookie_decoded, "sig", "&");
    set var.key_id = subfield(var.cookie_decoded, "kid", "&");
    set var.user_id = subfield(var.cookie_decoded, "uid", "&");
    set var.decision = subfield(var.cookie_decoded,"dec","&");

    if (var.user_id != var.authed_user_id) {
      set var.decision = "anon";
      log var.logger_prefix + "User " + var.authed_user_id + " denied while using a token generated for user " + var.user_id;
      
    } else if (!table.lookup(solution_waitingroom_signingkeys, var.key_id)) {
      set var.decision = "anon";
      log var.logger_prefix + "Unable to check signature due to missing key " + var.key_id;

    } else {
      set var.string_to_sign = "dec=" + var.decision + "&exp=" + var.expires + "&uid=" + var.user_id + "&kid=" + var.key_id;
      
      # If cookie signature doesn't check out, treat as anon
      if (!digest.secure_is_equal(var.sig, digest.hmac_sha256(table.lookup(solution_waitingroom_signingkeys, var.key_id), var.string_to_sign))) {
        set var.decision = "anon";
      }
    }
    
    # Actions for cookies that have reached their 'expiry time'
    if (time.is_after(now, std.integer2time(var.expires))) {
      
      # If the user has been allowed, revert to anon
      if (var.decision == "allow") {
        log var.logger_prefix + "Expired allow token reverted to anon";
        set var.decision = "anon";

      # If the user is waiting, they've now waited their turn
    	# so reveal the cookie decision
      } else if (var.decision == "wait") {
    	  set var.seed = std.strtol(substr(var.sig,0,8),16);
        set var.decision = if (randombool_seeded(var.percentage, 100, var.seed), "allow", "re-wait");
      }
    }
  }
  
  log var.logger_prefix + "Waiting room state: " + var.decision;

  # Set a cookie if appropriate
  if (var.decision == "anon" || var.decision == "allow" || var.decision == "re-wait") {
    set var.duration_key = if (var.decision == "allow", "allow_period_timeout", "wait_period_duration");
    set var.duration = std.atoi(table.lookup(solution_waitingroom_config, var.duration_key, "30"));

    set var.expires = now;
    if (var.decision == "allow") {
      set var.expires += var.duration;
    } else {
      set var.expires /= var.duration;
      set var.expires *= var.duration;
      set var.expires += var.duration;
      set var.expires += var.duration;
    }
    
    set var.key_id = table.lookup(solution_waitingroom_config, "active_key", "key1");
    set var.string_to_sign = "dec=" + if (var.decision == "allow", "allow", "wait") + "&exp=" + var.expires + "&uid=" + var.authed_user_id + "&kid=" + var.key_id;
    set var.sig = digest.hmac_sha256(table.lookup(solution_waitingroom_signingkeys, var.key_id), var.string_to_sign);
    set req.http.waitingroom_new_cookie = "waiting_room=" + digest.base64(var.string_to_sign + "&sig=" + var.sig) + "; path=/; max-age=" + table.lookup(solution_waitingroom_config, "cookie_lifetime", "7200");

  } else if (var.decision == "deny") {
    set req.http.waitingroom_new_cookie = "waiting_room=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
  }

  # Prevent normal request routing if decision is not 'allow'
  if (var.decision == "anon") {
    error 718 "waitingroom:startwaiting";
  } else if (var.decision == "wait" || var.decision == "re-wait") {
    error 718 "waitingroom:keepwaiting";
  } else if (var.decision == "deny") {
    error 718 "waitingroom:deny";
  }
}