Difference between revisions of "LSL Protocol/Restrained Love Relay/Alpha Version"

From Second Life Wiki
Jump to navigation Jump to search
(fixed tryToGluePendingCommands (which does work in the release version). Thanks to Nano Siemens)
(removeFromPendingList allows removing of pending restrictions dune to the dialog in ask mode, !release deletes pending restrictions (e. g. if the sub leaves the device before accepting the dialog))
Line 48: Line 48:
// ---------------------------------------------------
// ---------------------------------------------------
   
   
integer nMode;
integer mode;


list lRestrictions; // restrictions currently applied (without the "=n" part)
list restrictions; // restrictions currently applied (without the "=n" part)
key kSource;        // UUID of the object I'm commanded by, always equal to NULL_KEY if lRestrictions is empty, always set if not
key source;        // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not


string sPendingName; // name of initiator of pending request (first request of a session in mode 1)
string pendingName; // name of initiator of pending request (first request of a session in mode 1)
key sPendingId;      // UUID of initiator of pending request (first request of a session in mode 1)
key pendingId;      // UUID of initiator of pending request (first request of a session in mode 1)
string sPendingMessage; // message of pending request
string pendingMessage; // message of pending request
integer sPendingTime;
integer pendingTime;


// used on login
// used on login
Line 65: Line 65:
key    lastForceSitDestination;
key    lastForceSitDestination;
integer lastForceSitTime;
integer lastForceSitTime;


// ---------------------------------------------------
// ---------------------------------------------------
//              Low Level Communication
//              Helper functions
// ---------------------------------------------------
// ---------------------------------------------------
   
   
Line 78: Line 79:
     }
     }
}
}
// ---------------------------------------------------
//              Low Level Communication
// ---------------------------------------------------
   
   
// acknowledge or reject
// acknowledge or reject
Line 91: Line 99:
}
}


// get current mode as string
string getModeDescription()
{
    if (nMode == 0) return "RLV Relay is OFF";
    else if (nMode == 1) return "RLV Relay is ON (permission needed)";
    else return "RLV Relay is ON (auto-accept)";
}


// check that this command is for us and not someone else
// check that this command is for us and not someone else
Line 127: Line 128:


     // are we already under command by this object?
     // are we already under command by this object?
     if (kSource == id)
     if (source == id)
     {
     {
         return TRUE;
         return TRUE;
Line 133: Line 134:


     // are we not under command by any object but were we forced to sit on this object recently?
     // are we not under command by any object but were we forced to sit on this object recently?
     if ((kSource == NULL_KEY) && (id == lastForceSitDestination))
     if ((source == NULL_KEY) && (id == lastForceSitDestination))
     {
     {
         debug("on last force sit target");
         debug("on last force sit target");
Line 252: Line 253:
integer tryToGluePendingCommands(key id, string commands)
integer tryToGluePendingCommands(key id, string commands)
{
{
     if ((sPendingId == id) && (sPendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime()))
     if ((pendingId == id) && (pendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime()))
     {
     {
         debug("Gluing " + (string) sPendingMessage + " with " + commands);
         debug("Gluing " + (string) pendingMessage + " with " + commands);
         sPendingMessage = sPendingMessage + "|" + commands;
         pendingMessage = pendingMessage + "|" + commands;
         return TRUE;
         return TRUE;
     }
     }
Line 265: Line 266:
handleCommandsWhichAreAcceptedOutOfRange(key id, string message)
handleCommandsWhichAreAcceptedOutOfRange(key id, string message)
{
{
     if (id != kSource)
     if (id != source)
     {
     {
         return;
         return;
Line 291: Line 292:
{
{
     // is it switched off?
     // is it switched off?
     if (nMode == MODE_OFF)
     if (mode == MODE_OFF)
     {
     {
         return FALSE;
         return FALSE;
Line 326: Line 327:


     // ask in permission-request-mode and/OR in case the object identity is suspisous.
     // ask in permission-request-mode and/OR in case the object identity is suspisous.
     if (nMode == MODE_ASK || !trustworthy)
     if (mode == MODE_ASK || !trustworthy)
     {
     {
         sPendingId=id;
         pendingId=id;
         sPendingName=name;
         pendingName=name;
         sPendingMessage = message;
         pendingMessage = message;
         sPendingTime = llGetUnixTime();
         pendingTime = llGetUnixTime();
         if (llKey2Name(llGetOwnerKey(id)) != "")
         if (llKey2Name(llGetOwnerKey(id)) != "")
         {
         {
Line 392: Line 393:
     string behav=llList2String (tokens_command, 0); // @getattach:skull
     string behav=llList2String (tokens_command, 0); // @getattach:skull
     string param=llList2String (tokens_command, 1); // 2222
     string param=llList2String (tokens_command, 1); // 2222
     integer ind=llListFindList (lRestrictions, [behav]);
     integer ind=llListFindList (restrictions, [behav]);


debug("param=" + param);
     if (param=="n" || param=="add") // add to restrictions
 
     if (param=="n" || param=="add") // add to lRestrictions
     {
     {
         if (ind<0) lRestrictions+=[behav];
         if (ind<0) restrictions+=[behav];
         kSource=id; // we know that kSource is either NULL_KEY or id already
         source=id; // we know that source is either NULL_KEY or id already
     }
     }
     else if (param=="y" || param=="rem") // remove from lRestrictions
     else if (param=="y" || param=="rem") // remove from restrictions
     {
     {
         if (ind>-1) lRestrictions=llDeleteSubList (lRestrictions, ind, ind);
         if (ind > -1) restrictions=llDeleteSubList (restrictions, ind, ind);
         if (llGetListLength (lRestrictions)==0) kSource=NULL_KEY;
         if (llGetListLength (restrictions)==0) source=NULL_KEY;
         removeFromPendingList(behav);
         removeFromPendingList(behav);
     }
     }
Line 448: Line 447:


     // clear lastForceSitDestination in case we are now prevented from standing up and
     // clear lastForceSitDestination in case we are now prevented from standing up and
     // the force sit was long ago. Note: lRestrictions is checked to prevent the
     // the force sit was long ago. Note: restrictions is checked to prevent the
     // clearance in case @unsit is just send again on login
     // clearance in case @unsit is just send again on login
     if (behav == "@unsit")
     if (behav == "@unsit")
     {
     {
         if (llListFindList(lRestrictions, ["@unsit"]) < 0)
         if (llListFindList(restrictions, ["@unsit"]) < 0)
         {
         {
             if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime())
             if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime())
Line 483: Line 482:
executeMetaCommand(string cmd_id, string id, string command)
executeMetaCommand(string cmd_id, string id, string command)
{
{
     if (command==PREFIX_METACOMMAND+"version") // checking relay version
     if (command==PREFIX_METACOMMAND + "version") // checking relay version
     {
     {
         ack(cmd_id, id, command, (string)RLVRS_PROTOCOL_VERSION);
         ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION);
     }
     }
     else if (command==PREFIX_METACOMMAND+"release") // release all the restrictions (end session)
     else if (command==PREFIX_METACOMMAND + "release") // release all the restrictions (end session)
     {
     {
         releaseRestrictions();
         releaseRestrictions();
Line 494: Line 493:
}
}


// removes a restriction from the list of pending commands
removeFromPendingList(string behav)
removeFromPendingList(string behav)
{
{
     // TODO: implement me   
    string search = behav + "=";
 
     // don't do the expensive parsing (string operations are very slow in pre-
    // mono LSL) in case we can detect fast that this one is not in the list.
    if (llSubStringIndex(pendingMessage, search) < 0)
    {
        return;
    }
 
    list tokens = llParseString2List(pendingMessage, [","], []);
    list commands = llParseString2List(llList2String(tokens, 2), ["|"], []);
    integer modified = FALSE;
 
    integer len = llGetListLength(commands);
    integer i;
    for (i = len - 1; i >= 0; i--)
    {
        string cmd = llList2String(commands, i);
        if (llSubStringIndex(cmd, search) == 0)
        {
            if (llSubStringIndex(cmd, "=n") > -1 || llSubStringIndex(cmd, "=add") > -1)
            {
                commands = llDeleteSubList(commands, i, i);
                modified = TRUE;
            }
        }
    }
 
    if (modified)
    {
        if (llGetListLength(commands) > 0)
        {
            pendingMessage = llList2String(tokens, 0) + "," + llList2String(tokens, 1) + "," + llDumpList2String(commands, "|");
        }
        else
        {
            clearPendingMessages();
        }
    }
 
}
}


Line 503: Line 542:
releaseRestrictions ()
releaseRestrictions ()
{
{
     kSource=NULL_KEY;
     source = NULL_KEY;
     integer i;
     integer i;
     integer len=llGetListLength (lRestrictions);
     integer len = llGetListLength (restrictions);
     for (i=0; i<len; ++i)
     for (i = 0; i < len; ++i)
     {
     {
         sendRLCmd(llList2String (lRestrictions, i)+"=y");
         sendRLCmd(llList2String (restrictions, i) + "=y");
     }
     }
     lastForceSitDestination = NULL_KEY;
     lastForceSitDestination = NULL_KEY;
     lRestrictions = [];
     restrictions = [];
     loginPendingForceSit = FALSE;
     loginPendingForceSit = FALSE;
    clearPendingMessages();
}
// deletes the list of pending messsages
clearPendingMessages()
{
    // clear pending request
    pendingName = "";
    pendingId = NULL_KEY;
    pendingMessage = "";
    pendingTime = 0;
}
// ---------------------------------------------------
//            mode and dialog handling
// ---------------------------------------------------
// get current mode as string
string getModeDescription()
{
    if (mode == 0) return "RLV Relay is OFF";
    else if (mode == 1) return "RLV Relay is ON (permission needed)";
    else return "RLV Relay is ON (auto-accept)";
}
// process the Yes/No buttons of the permission dialog
processDialogResponse(key id, string message)
{
    if (id != llGetOwner())
    {
        return; // only accept dialog responses from the owner
    }
    if (pendingId != NULL_KEY)
    {
        if (message=="Yes") // pending request authorized => process it
        {
            execute(pendingName, pendingId, pendingMessage);
        }
        clearPendingMessages();
    }
}
}


   
   
Line 521: Line 608:
   
   
init() {
init() {
     nMode = 1;
     mode = 1;
     kSource = NULL_KEY;
     source = NULL_KEY;
     lRestrictions = [];
     restrictions = [];
     sPendingId = NULL_KEY;
     pendingId = NULL_KEY;
     sPendingName = "";
     pendingName = "";
     sPendingMessage = "";
     pendingMessage = "";
     llListen (RLVRS_CHANNEL, "", "", "");
     llListen (RLVRS_CHANNEL, "", "", "");
     llListen (DIALOG_CHANNEL, "", llGetOwner(), "");
     llListen (DIALOG_CHANNEL, "", llGetOwner(), "");
     llOwnerSay (getModeDescription());
     llOwnerSay (getModeDescription());
     debug((string) llGetFreeMemory());
     debug("Free Memory: " + (string) llGetFreeMemory());
}
}


// sends the known restrictions (again) to the RL-viewer
// sends the known restrictions (again) to the RL-viewer
Line 538: Line 626:
{
{
     integer i;
     integer i;
     integer len=llGetListLength(lRestrictions);
     integer len=llGetListLength(restrictions);
     string restr;
     string restr;
     debug("kSource=" + (string) kSource);
     debug("source=" + (string) source);
     for (i=0; i<len; ++i)
     for (i=0; i<len; ++i)
     {
     {
         restr=llList2String(lRestrictions, i);
         restr=llList2String(restrictions, i);
         debug("restr=" + restr);
         debug("restr=" + restr);
         sendRLCmd(restr+"=n");
         sendRLCmd(restr+"=n");
Line 557: Line 645:
{
{
     loginWaitingForPong = FALSE;
     loginWaitingForPong = FALSE;
     if (kSource != NULL_KEY)
     if (source != NULL_KEY)
     {
     {
         ack("ping", kSource, "ping", "ping");
         ack("ping", source, "ping", "ping");
         timerTickCounter = 0;
         timerTickCounter = 0;
         llSetTimerEvent(1.0);
         llSetTimerEvent(1.0);
Line 571: Line 659:
     if (target == NULL_KEY)
     if (target == NULL_KEY)
     {
     {
         target = kSource;
         target = source;
     }
     }
     debug("Force sit during login on " + (string) target);
     debug("Force sit during login on " + (string) target);
Line 581: Line 669:
     state_entry()
     state_entry()
     {
     {
        llOwnerSay("@clear");
         init();
         init();
     }
     }
Line 588: Line 677:
         // relogging, we must refresh the viewer and ping the object if any
         // relogging, we must refresh the viewer and ping the object if any
         // if mode is not OFF, fire all the stored restrictions
         // if mode is not OFF, fire all the stored restrictions
         if (nMode)
         if (mode)
         {
         {
             reinforceKnownRestrictions();
             reinforceKnownRestrictions();
Line 646: Line 735:
             }
             }
          
          
             if (nMode== MODE_OFF)
             if (mode== MODE_OFF)
             {
             {
                 debug("deactivated - ignoring commands");
                 debug("deactivated - ignoring commands");
Line 657: Line 746:
             }
             }


             debug("Got message (active world object " + (string) kSource + "): name=" + name+ "id=" + (string) id + " message=" + message);
             debug("Got message (active world object " + (string) source + "): name=" + name+ "id=" + (string) id + " message=" + message);
   
   
             if (kSource != NULL_KEY && kSource != id)
             if (source != NULL_KEY && source != id)
             {
             {
                 debug("already used by another object => reject");
                 debug("already used by another object => reject");
Line 669: Line 758:
             if (!isObjectKnow(id))
             if (!isObjectKnow(id))
             {
             {
                 debug("asking for permission because kSource is NULL_KEY");
                 debug("asking for permission because source is NULL_KEY");
                 if (!verifyPermission(id, name, message))
                 if (!verifyPermission(id, name, message))
                 {
                 {
Line 676: Line 765:
             }
             }


             debug("Executing: " + (string) kSource);
             debug("Executing: " + (string) source);
             execute(name, id, message);
             execute(name, id, message);
         }
         }
         else if (channel==DIALOG_CHANNEL)
         else if (channel==DIALOG_CHANNEL)
         {
         {
             if (id != llGetOwner())
             processDialogResponse(id, message);
            {
                return; // only accept dialog responses from the owner
            }
            if (sPendingId!=NULL_KEY)
            {
                if (message=="Yes") // pending request authorized => process it
                {
                    execute(sPendingName, sPendingId, sPendingMessage);
                }
 
                // clear pending request
                sPendingName="";
                sPendingId=NULL_KEY;
                sPendingMessage="";
            }
         }
         }
     }
     }
Line 703: Line 777:
     {
     {
         // touched by user => cycle through OFF/ON_PERMISSION/ON_ALWAYS modes
         // touched by user => cycle through OFF/ON_PERMISSION/ON_ALWAYS modes
         key toucher=llDetectedKey(0);
         key toucher = llDetectedKey(0);
         if (toucher==llGetOwner())
         if (toucher == llGetOwner())
         {
         {
             if (kSource != NULL_KEY)
             if (source != NULL_KEY)
             {
             {
                 llOwnerSay("Sorry, you cannot change the relay mode while it is locked.");
                 llOwnerSay("Sorry, you cannot change the relay mode while it is locked.");
                 return;
                 return;
             }
             }
             ++nMode;
             mode++;
             if (nMode>2) nMode=0;
             if (mode > 2)
             if (nMode==MODE_OFF) releaseRestrictions ();
            {
             llOwnerSay (getModeDescription());
                mode = 0;
            }
             if (mode == MODE_OFF) {
                releaseRestrictions();
            }
             llOwnerSay(getModeDescription());
         }
         }
     }
     }

Revision as of 17:50, 27 July 2008

Note: This is an untested, incomplete alpha version. This page is for developers and contributors.

Please use the code on the page Reference Implementation. <lsl> integer ALLOW_REMOVE_DETACH = TRUE; integer DEBUG = FALSE;


//~ RestrainedLife Viewer Relay Script example code //~ By Marine Kelley, Maike Short, Felis Darwin

//~ This code is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, //~ completeness or performance. It may only be distributed in its full source code, //~ this header and disclaimer and is not to be sold.


// --------------------------------------------------- // Constants // ---------------------------------------------------

integer RLVRS_PROTOCOL_VERSION = 1014; // version of the protocol, stated on the specification page

string PREFIX_RL_COMMAND = "@"; string PREFIX_METACOMMAND = "!";

integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer DIALOG_CHANNEL = -1812220409; // RLVDI in numbers

integer MAX_OBJECT_DISTANCE = 20; // 20m is llSay distance integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds

integer PERMISSION_DIALOG_TIMEOUT = 30;

integer LOGIN_DELAY_WAIT_FOR_PONG = 10; integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;

integer PING_INTERVAL = 60; //== Time between pings

integer MODE_OFF = 0; integer MODE_ASK = 1; integer MODE_AUTO = 2;

// --------------------------------------------------- // Variables // ---------------------------------------------------

integer mode;

list restrictions; // restrictions currently applied (without the "=n" part) key source; // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not

string pendingName; // name of initiator of pending request (first request of a session in mode 1) key pendingId; // UUID of initiator of pending request (first request of a session in mode 1) string pendingMessage; // message of pending request integer pendingTime;

// used on login integer timerTickCounter; // count the number of time events on login (forceSit has to be delayed a bit) integer loginWaitingForPong; integer loginPendingForceSit;

key lastForceSitDestination; integer lastForceSitTime;


// --------------------------------------------------- // Helper functions // ---------------------------------------------------


debug(string x) {

   if (DEBUG)
   {
       llOwnerSay("DEBUG: " + x);
   }

}


// --------------------------------------------------- // Low Level Communication // ---------------------------------------------------


// acknowledge or reject ack(string cmd_id, key id, string cmd, string ack) {

   llSay(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);

}

// cmd begins with a '@' sendRLCmd(string cmd) {

   llOwnerSay(cmd);

}


// check that this command is for us and not someone else integer verifyWeAreTarget(string message) {

   list tokens = llParseString2List(message, [","], []);
   if (llGetListLength(tokens) == 3) // this is a normal command
   {
     if (llList2String(tokens, 1) == llGetOwner()) // talking to me ?
     {
        return TRUE;
     }
   }
   return FALSE;

}

// --------------------------------------------------- // Permission Handling // ---------------------------------------------------

// are we already under command by this object? integer isObjectKnow(key id) {

   // first some error handling
   if (id == NULL_KEY)
   {
       return FALSE;
   }
   // are we already under command by this object?
   if (source == id)
   {
       return TRUE;
   }
   // are we not under command by any object but were we forced to sit on this object recently?
   if ((source == NULL_KEY) && (id == lastForceSitDestination))
   {
       debug("on last force sit target");
       if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT > llGetUnixTime())
       {
           debug("and recent enough to auto accept");
           return TRUE;
       }
   }
   return FALSE;

}


// check whether the object is in llSay distance. // The specification requires llSay instead of llShout or llRegionSay // to be used to limit the range. But this has to be checked here again // because the objects are not trustworthy. integer isObjectNear(key id) {

   vector myPosition = llGetRootPosition();
   list temp = llGetObjectDetails(id, ([OBJECT_POS]));
   vector objPostition = llList2Vector(temp,0);
   float distance = llVecDist(objPostition, myPosition);
   return distance <= MAX_OBJECT_DISTANCE;

}

// do a basic check on the identity of the object trying to issue a command integer isObjectIdentityTrustworthy(key id) {

   key parcel_owner=llList2Key (llGetParcelDetails (llGetPos (), [PARCEL_DETAILS_OWNER]), 0);
   key parcel_group=llList2Key (llGetParcelDetails (llGetPos (), [PARCEL_DETAILS_GROUP]), 0);
   key object_owner=llGetOwnerKey(id);
   key object_group=llList2Key (llGetObjectDetails (id, [OBJECT_GROUP]), 0);
   debug("owner= " + (string) parcel_owner + " / " + (string) object_owner);
   debug("group= " + (string) parcel_group + " / " + (string) object_group);
   if (object_owner==llGetOwner ()        // IF I am the owner of the object
     || object_owner==parcel_owner        // OR its owner is the same as the parcel I'm on
     || object_group==parcel_group        // OR its group is the same as the parcel I'm on
   )
   {
       return TRUE;
   }
   return FALSE;

}


// Is this a simple request for information or a meta command like !release? integer isSimpleRequest(list list_of_commands) {

   integer len = llGetListLength(list_of_commands);
   integer i;
   // now check every single atomic command
   for (i=0; i < len; ++i)
   {
       string command = llList2String(list_of_commands, i);
       if (!isSimpleAtomicCommand(command))
       {
          return FALSE;
       }
   }
   // all atomic commands passed the test
   return TRUE;

}

// is this a simple atmar command // (a command which only queries some information or releases restrictions) // (e. g.: cmd ends with "=" and a number (@version, @getoutfit, @getattach) or is a !-meta-command) integer isSimpleAtomicCommand(string cmd) {

   // check right hand side of the "=" - sign
   integer index = llSubStringIndex (cmd, "=");
   if (index > -1) // there is a "=" 
   {
       // check for a number after the "="
       string param = llGetSubString (cmd, index + 1, -1);
       if ((integer)param!=0 || param=="0") // is it an integer (channel number)?
       {
           return TRUE;
       }
       // removing restriction
       if ((param == "y") || (param == "rem"))
       {
           return TRUE;
       }
   }
   // check for a leading ! (meta command)
   if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0)
   {
       return TRUE;
   }
   // check for @clear
   // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login
   // (but we need this check here because "!release|@clear" is a BROKEN attempt to work around
   // a bug in the first relay implementation. You should refuse to use relay versions < 1013
   // instead.)
   if (cmd == "@clear")
   {
       return TRUE;
   }
 
   // this one is not "simple".
   return FALSE;

}

// If we already have commands from this object pending // because of a permission request dialog, just add the // new commands at the end. // Note: We use a timeout here because the player may // have "ignored" the dialog. integer tryToGluePendingCommands(key id, string commands) {

   if ((pendingId == id) && (pendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime()))
   {
       debug("Gluing " + (string) pendingMessage + " with " + commands);
       pendingMessage = pendingMessage + "|" + commands;
       return TRUE;
   }
   return FALSE;

}


// accept !release even if out of range handleCommandsWhichAreAcceptedOutOfRange(key id, string message) {

   if (id != source)
   {
       return;
   }
   list tokens = llParseString2List (message, [","], []);
   if (llGetListLength (tokens) < 3)
   {
       return;
   }
   string commands = llList2String(tokens, 2);
   list list_of_commands = llParseString2List(commands, ["|"], []);
   if (llListFindList(list_of_commands, ["!release"]) > -1)
   {
       debug("accepted !release although it was out of range");
       releaseRestrictions();
   }

}


// verifies the permission. This includes mode // (off, permission, auto) of the relay and the // identity of the object (owned by parcel people). integer verifyPermission(key id, string name, string message) {

   // is it switched off?
   if (mode == MODE_OFF)
   {
       return FALSE;
   }
   list tokens = llParseString2List (message, [","], []);
   if (llGetListLength (tokens) < 3)
   {
       return FALSE;
   }
   string commands = llList2String(tokens, 2);
   list list_of_commands = llParseString2List(commands, ["|"], []);
   // if we are already having a pending permission-dialog request for THIS object,
   // just add the new commands at the end of the pending command list.
   if (tryToGluePendingCommands(id, commands))
   {
       return FALSE;
   }
   // accept harmless commands silently
   if (isSimpleRequest(list_of_commands))
   {
       return TRUE;
   }
   // check whether this object belongs here
   integer trustworthy = isObjectIdentityTrustworthy(id);
   string warning = "";
   if (!trustworthy)
   {
       warning = "\n\nWARNING: This object is not owned by the people owning this parcel. Unless you know the owner, you should deny this request.";
   }
   // ask in permission-request-mode and/OR in case the object identity is suspisous.
   if (mode == MODE_ASK || !trustworthy)
   {
       pendingId=id;
       pendingName=name;
       pendingMessage = message;
       pendingTime = llGetUnixTime();
       if (llKey2Name(llGetOwnerKey(id)) != "")
       {
           name += " (owned by " + llKey2Name(llGetOwnerKey(id)) + ")";
       }
       llDialog (llGetOwner(), name + " would like control your viewer." + warning + ".\n\nDo you accept ?", ["Yes", "No"], DIALOG_CHANNEL);
       debug("Asking for permission");
       return FALSE;
   }
   return TRUE;

}


// --------------------------------------------------- // Executing of commands // ---------------------------------------------------

// execute a non-parsed message // this command could be denied here for policy reasons, (if it were implemenetd) // but this time there will be an acknowledgement execute(string name, key id, string message) {

   list tokens=llParseString2List (message, [","], []);
   if (llGetListLength (tokens)==3) // this is a normal command
   {
       string cmd_id=llList2String (tokens, 0); // CheckAttach
       key target=llList2Key (tokens, 1); // UUID
       if (target==llGetOwner ()) // talking to me ?
       {
           list list_of_commands=llParseString2List (llList2String (tokens, 2), ["|"], []);
           integer len=llGetListLength (list_of_commands);
           integer i;
           string command;
           string prefix;
           for (i=0; i<len; ++i) // execute every command one by one
           {
               // a command is a RL command if it starts with '@' or a metacommand if it starts with '!'
               command=llList2String (list_of_commands, i);
               prefix=llGetSubString (command, 0, 0);

               if (prefix==PREFIX_RL_COMMAND) // this is a RL command
               {
                   executeRLVCommand(cmd_id, id, command);
               }
               else if (prefix==PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself
               {
                   executeMetaCommand(cmd_id, id, command);
               }
           }
       }
   }

}

// executes a command for the restrained life viewer // with some additinal magic like book keeping executeRLVCommand(string cmd_id, string id, string command) {

   // we need to know whether whether is a rule or a simple command
   list tokens_command=llParseString2List (command, ["="], []);
   string behav=llList2String (tokens_command, 0); // @getattach:skull
   string param=llList2String (tokens_command, 1); // 2222
   integer ind=llListFindList (restrictions, [behav]);
   if (param=="n" || param=="add") // add to restrictions
   {
       if (ind<0) restrictions+=[behav];
       source=id; // we know that source is either NULL_KEY or id already
   }
   else if (param=="y" || param=="rem") // remove from restrictions
   {
       if (ind > -1) restrictions=llDeleteSubList (restrictions, ind, ind);
       if (llGetListLength (restrictions)==0) source=NULL_KEY;
       removeFromPendingList(behav);
   }
   else if (param == "force" && !ALLOW_REMOVE_DETACH)
   {
       debug("force: " + behav);
       list temp = llParseString2List(behav, [":"], []);
       string commandName = llList2String (temp, 0); // @sit
       if (commandName == "@detach" || commandName == "@remoutfit")
       {
           debug("rejecting remove/detach");
           llWhisper(0, "Not stripping");
           ack(cmd_id, id, command, "ko");
           return;
       }
   }
   workaroundForAtClear(command);
   rememberForceSit(command);
   sendRLCmd(command); // execute command
   ack(cmd_id, id, command, "ok"); // acknowledge

}

// check for @clear // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login // (but we need this check here because "!release|@clear" is a BROKEN attempt to work around // a bug in the first relay implementation. You should refuse to use relay versions < 1013 // instead.) workaroundForAtClear(string command) {

   if (command == "@clear")
   {
       releaseRestrictions();
   }

}

// remembers the time and object if this command is a force sit rememberForceSit(string command) {

   list tokens_command=llParseString2List (command, ["="], []);
   string behav=llList2String (tokens_command, 0); // @sit:<uuid>
   string param=llList2String (tokens_command, 1); // force
   // clear lastForceSitDestination in case we are now prevented from standing up and
   // the force sit was long ago. Note: restrictions is checked to prevent the
   // clearance in case @unsit is just send again on login
   if (behav == "@unsit")
   {
       if (llListFindList(restrictions, ["@unsit"]) < 0)
       {
           if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime())
           {
               debug("clearing lastForceSitDestination");
               lastForceSitDestination = NULL_KEY;
           }
       }
   }
   
   if (param != "force")
   {
       return;
   }
   tokens_command=llParseString2List(behav, [":"], []);
   behav=llList2String (tokens_command, 0); // @sit
   param=llList2String (tokens_command, 1); // <uuid>
   debug("'force'-command:" + behav + "/" + param);
   if (behav != "@sit")
   {
       return;
   }
   lastForceSitDestination = (key) param;
   lastForceSitTime = llGetUnixTime();
   debug("remembered force sit");

}

// executes a meta command which is handled by the relay itself executeMetaCommand(string cmd_id, string id, string command) {

   if (command==PREFIX_METACOMMAND + "version") // checking relay version
   {
       ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION);
   }
   else if (command==PREFIX_METACOMMAND + "release") // release all the restrictions (end session)
   {
       releaseRestrictions();
       ack(cmd_id, id, command, "ok");
   }

}

// removes a restriction from the list of pending commands removeFromPendingList(string behav) {

   string search = behav + "=";
   // don't do the expensive parsing (string operations are very slow in pre- 
   // mono LSL) in case we can detect fast that this one is not in the list.
   if (llSubStringIndex(pendingMessage, search) < 0)
   {
       return;
   }
   list tokens = llParseString2List(pendingMessage, [","], []);
   list commands = llParseString2List(llList2String(tokens, 2), ["|"], []);
   integer modified = FALSE;
   integer len = llGetListLength(commands);
   integer i;
   for (i = len - 1; i >= 0; i--)
   {
       string cmd = llList2String(commands, i);
       if (llSubStringIndex(cmd, search) == 0)
       {
           if (llSubStringIndex(cmd, "=n") > -1 || llSubStringIndex(cmd, "=add") > -1)
           {
               commands = llDeleteSubList(commands, i, i);
               modified = TRUE;
           } 
       }
   }
   if (modified)
   {
       if (llGetListLength(commands) > 0)
       {
           pendingMessage = llList2String(tokens, 0) + "," + llList2String(tokens, 1) + "," + llDumpList2String(commands, "|");
       }
       else
       {
           clearPendingMessages();
       }
   }

}


// lift all the restrictions (called by !release and by turning the relay off) releaseRestrictions () {

   source = NULL_KEY;
   integer i;
   integer len = llGetListLength (restrictions);
   for (i = 0; i < len; ++i)
   {
       sendRLCmd(llList2String (restrictions, i) + "=y");
   }
   lastForceSitDestination = NULL_KEY;
   restrictions = [];
   loginPendingForceSit = FALSE;
   clearPendingMessages();

}


// deletes the list of pending messsages clearPendingMessages() {

   // clear pending request
   pendingName = "";
   pendingId = NULL_KEY;
   pendingMessage = "";
   pendingTime = 0;

}


// --------------------------------------------------- // mode and dialog handling // ---------------------------------------------------


// get current mode as string string getModeDescription() {

   if (mode == 0) return "RLV Relay is OFF"; 
   else if (mode == 1) return "RLV Relay is ON (permission needed)"; 
   else return "RLV Relay is ON (auto-accept)"; 

}


// process the Yes/No buttons of the permission dialog processDialogResponse(key id, string message) {

   if (id != llGetOwner())
   {
       return; // only accept dialog responses from the owner
   }
   if (pendingId != NULL_KEY)
   {
       if (message=="Yes") // pending request authorized => process it
       {
           execute(pendingName, pendingId, pendingMessage);
       }
       clearPendingMessages();
   }

}


// --------------------------------------------------- // initialisation and login handling // ---------------------------------------------------

init() {

   mode = 1;
   source = NULL_KEY;
   restrictions = [];
   pendingId = NULL_KEY;
   pendingName = "";
   pendingMessage = "";
   llListen (RLVRS_CHANNEL, "", "", "");
   llListen (DIALOG_CHANNEL, "", llGetOwner(), "");
   llOwnerSay (getModeDescription());
   debug("Free Memory: " + (string) llGetFreeMemory());

}


// sends the known restrictions (again) to the RL-viewer // (call this functions on login) reinforceKnownRestrictions() {

   integer i;
   integer len=llGetListLength(restrictions);
   string restr;
   debug("source=" + (string) source);
   for (i=0; i<len; ++i)
   {
       restr=llList2String(restrictions, i);
       debug("restr=" + restr);
       sendRLCmd(restr+"=n");
       if (restr=="@unsit")
       {
           loginPendingForceSit = TRUE;
       }
   }

}

// send a ping request and start a timer pingWorldObjectIfUnderRestrictions() {

   loginWaitingForPong = FALSE;
   if (source != NULL_KEY)
   {
       ack("ping", source, "ping", "ping");
       timerTickCounter = 0;
       llSetTimerEvent(1.0);
       loginWaitingForPong = TRUE;
   }

}

sendForceSitDuringLogin() {

   key target = lastForceSitDestination;
   if (target == NULL_KEY)
   {
       target = source;
   }
   debug("Force sit during login on " + (string) target);
   sendRLCmd ("@sit:"+(string)target+"=force");

}

default {

   state_entry()
   {
       llOwnerSay("@clear");
       init();
   }

   on_rez(integer start_param)
   {
       // relogging, we must refresh the viewer and ping the object if any
       // if mode is not OFF, fire all the stored restrictions
       if (mode)
       {
           reinforceKnownRestrictions();
           pingWorldObjectIfUnderRestrictions();
       }
       // remind the current mode to the user
       llOwnerSay(getModeDescription());
   }


   timer()
   {
       timerTickCounter++;
       debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit);
       if (loginWaitingForPong && (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_PONG))
       {
           llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device is not available.");
           loginWaitingForPong = FALSE;
           loginPendingForceSit = FALSE;
           releaseRestrictions();
       }
       if (loginPendingForceSit)
       {
           integer agentInfo = llGetAgentInfo(llGetOwner());
           if (agentInfo & AGENT_SITTING)
           {
               loginPendingForceSit = FALSE;
               debug("is sitting now");
           }
           else if (timerTickCounter == LOGIN_DELAY_WAIT_FOR_FORCE_SIT)
           {
               llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because sitting down again was not possible.");
               loginPendingForceSit = FALSE;
               releaseRestrictions();
           }
           else
           {
               sendForceSitDuringLogin();
           }
       }
       if (!loginPendingForceSit && !loginWaitingForPong)
       {
           llSetTimerEvent(0.0);
       }
   }

   listen(integer channel, string name, key id, string message)
   {
       if (channel==RLVRS_CHANNEL)
       {
           debug("LISTEN: " + message);
           if (!verifyWeAreTarget(message))
           {
              return;
           }
       
           if (mode== MODE_OFF)
           {
               debug("deactivated - ignoring commands");
               return; // mode is 0 (off) => reject
           }
           if (!isObjectNear(id)) 
           {
               handleCommandsWhichAreAcceptedOutOfRange(id, message);
               return;
           }
           debug("Got message (active world object " + (string) source + "): name=" + name+ "id=" + (string) id + " message=" + message);

           if (source != NULL_KEY && source != id)
           {
               debug("already used by another object => reject");
               return;
           }
           loginWaitingForPong = FALSE; // whatever the message, it is for me => it satisfies the ping request
           if (!isObjectKnow(id))
           {
               debug("asking for permission because source is NULL_KEY");
               if (!verifyPermission(id, name, message))
               {
                   return;
               }
           }
           debug("Executing: " + (string) source);
           execute(name, id, message);
       }
       else if (channel==DIALOG_CHANNEL)
       {
           processDialogResponse(id, message);
       }
   }

   touch_start(integer num_detected)
   {
       // touched by user => cycle through OFF/ON_PERMISSION/ON_ALWAYS modes
       key toucher = llDetectedKey(0);
       if (toucher == llGetOwner())
       {
           if (source != NULL_KEY)
           {
               llOwnerSay("Sorry, you cannot change the relay mode while it is locked.");
               return;
           }
           mode++;
           if (mode > 2)
           { 
               mode = 0;
           }
           if (mode == MODE_OFF) {
               releaseRestrictions();
           }
           llOwnerSay(getModeDescription());
       }
   }
   changed(integer change)
   {
       if (change & CHANGED_OWNER) 
       {
            llResetScript();
       }
   }

} </lsl>