LSL Protocol/Restrained Love Relay/Specification
Written and maintained by Marine Kelley.
This document is meant for people who want to create or modify in-world objects to use the features of someone else's RestrainedLove viewer, typically cages and pieces of furniture, which per definition are usually not owned by that person.
The RestrainedLove viewer only executes commands issued through llOwnerSay () messages. Therefore, in order to issue commands to someone using the viewer who does not own the object, that person must wear an attachment that relays commands after some security checks.
Why this spec ?
Many cages and furniture creators are interested in using its features such as sit, outfit, tp etc. These objects can be found in public places, or owned by friends... but as they are usually not owned by the user, the relay has to implement some basic security in order to avoid griefing. On top of it, the user does not want to be forced to switch to another relay when going to the next piece of furniture.
This is the purpose of this specification: To lay common rules so all the relays implementing it are compatible with all the furniture implementing it too. Without such a specification, one cage/furniture would talk to the relay specifically made to operate with it and that's all, eventually making the creator stay behind because people rather use standard objects than proprietary closed ones.
Here is a sample use case:
- User is wearing a Relay
- User enters a cage in a public area
- Cage sends a chat message on a known private channel (for instance "@tploc=n")
- Relay receives the message, decides to repeat the command to the user and blocks their ability to teleport from the map with an llOwnerSay ("@tploc=n");
- User is allowed to get out after some time, the cage issues a "!release" command
Without the relay, the cage could never prevent the user from teleporting since it doesn't belong to them.
Here are the informal requirements for a relay (formal requirements below).
Any object sending commands over the channel the relay listens to is likely to harm the user if there is no security implemented in the former. For instance, one could rez an object that sends a "@remoutfit=force" command over the chat channel to force an avatar to get naked in front of everyone without a warning. Of course nobody wants that, so basic security is needed.
Security often implies control (access lists, switch, permissions...) so the user must be given some basic control over what kind of objects are allowed to issue commands.
Some users will prefer wearing a dedicated attachment that they can unwear any time they want, others will be required to have the relay locked on them so they cannot detach them, others will want the script only... It is important to keep those differences in mind when deciding about the permissions of the relay. However, it is the user's responsibility to choose the relay that suits their needs best.
According to the level of complexity and support of the relay, the creator is allowed to either provide it for free (open/close source) or to sell it, as long as it implements all the formal requirements.
How hard is it to implement such a specification?
That depends on what you do. Furniture/cage makers will find it very easy for it only comes down to sending commands over a chat channel and getting feedback. Relay makers will find it harder but then again, that depends on the level of security and user-friendliness they wish to offer. But make no mistake, the relay is what does almost all the job (along with the viewer), because there will be many more kinds of furniture and cages than relays around.
Why do other people need to write such a relay?
Couldn't you write it yourself and publish it?
Of course I could, and there is even a working code for a basic relay at Reference Implementation. However:
- The protocol is likely to improve because nobody sees the future
- One object only would not suit everyone's needs
- It would have to implement perfect security and perfect user-friendliness, in all cases
- It would of course have to be perfectly scripted, without any bug whatsoever
The security and user-friendliness are the key parts here. Some users will prefer to be safe from griefing, others will prefer a good user interface, others will like a lot of features, others will want to move the script elsewhere... Everyone has their own tastes so there can be no one-size-fits-all relay.
In-world objects send chat message over a channel (common to every relay and furniture), that the relay(s) acknowledges or not. If the message is a correct command and passes whatever security checks the relay implements, the latter repeats it as an llOwnerSay ().
When the session ends, possibly after several relogs, the object clears all the restrictions it has put the user under.
Here is a basic example of messages exchanged between an avatar (id "9213...") and a world object (id "7adf...") :
Object : CmdTest,9213f69a-ed7d-4a70-907a-7dba88c8831a,@tploc=n Relay executes llOwnerSay ("@tploc=n"); Relay : CmdTest,7adf6218-ab26-8566-8387-660133840794,@tploc=n,ok
Object : BunchoCommands,9213f69a-ed7d-4a70-907a-7dba88c8831a,@tploc=n|@tplm=n|@tplure=n|@remoutfit:shoes=force Relay executes llOwnerSay ("@tploc=n"); Relay executes llOwnerSay ("@tplm=n"); Relay executes llOwnerSay ("@tplure=n"); Relay : BunchoCommands,7adf6218-ab26-8566-8387-660133840794,@tploc=n,ok Relay : BunchoCommands,7adf6218-ab26-8566-8387-660133840794,@tplm=n,ok Relay : BunchoCommands,7adf6218-ab26-8566-8387-660133840794,@tplure=n,ok Relay : BunchoCommands,7adf6218-ab26-8566-8387-660133840794,@remoutfit:shoes=force,ko
After a relog : Relay : ping,7adf6218-ab26-8566-8387-660133840794,ping,ping Object : ping,9213f69a-ed7d-4a70-907a-7dba88c8831a,!pong (UUID found with llGetOwnerKey(id), where id is the sender-parameter of the listen-event)
Common requirements to the Relay and the in-world object
- The chat channel used by both the Relay and the in-world object is -1812221819. That's "RLVRS" ("RestrainedLove Viewer Relay Script") translated from alphabet to numbers.
- Messages on the channel are described here in a pseudo Backus-Naur Form :
- Messages from in-world object to Relay (3 tokens) :
- message ::= <cmd_name>,<user_uuid>,<list_of_commands>
- list_of_commands ::= <command>[|<list_of_commands>] (list_of_commands is *lowercase*)
- command ::= <rl_command> or <meta-command>
- rl_command ::= @behav[:option][=param]
- meta-command ::= !version or !release or !pong or !implversion or !handover/<hand-over parameter> or !who/<who parameter>
- Messages from in-world object to Relay (3 tokens) :
- Messages from Relay to in-world object (4 tokens) :
- message ::= <cmd_name>,<object_uuid>,<command>,<reply> (cmd_name is equal to the one in the incoming message)
- reply ::= ok or ko or ping or <protocol_version> or <implementation_version>
- protocol_version ::= integer (it is the version of the specification, not of the script)
- Messages from Relay to in-world object (4 tokens) :
- The effect of the "!release" meta-command is to wipe out all the restrictions issued by the object which sends it.
- The effect of the "!version" meta-command is to send the version of the protocol the Relay implements. See below.
- The effect of the "ping" message from the Relay to the object is to check the latter is still available. If not, release the user to avoid having orphaned rules.
- Notice that acknowledgments do not apply to the list of commands but to one command only. Therefore a list of N commands gives N acknowledgment messages in return (at most).
In plain English :
- <cmd_name> is the name of the command, decided by the object. It will be used to find out which command has been acknowledged, therefore it must be repeated exactly by the Relay (without changing its case). An exception to the freedom of choice of the <cmd_name> token is the "ping" reserved name, see below.
- <user_uuid> is the UUID of the avatar owning the Relay.
- <object_uuid> is the UUID of the in-world object. Notice that we never need the UUID of the Relay as it's usually an attachment, prone to change its id after each relog.
- <list_of_commands> is a list of RL commands separated by pipes ('|'). It can be a single command (meaning no pipe present).
- <command> is either a regular RL command (@behav:option=param) or a meta-command, aimed at the Relay itself (!version, !release and !pong)
- Commands are separated by pipes ('|') here, but if they must be sent in the same llOwnerSay to the viewer they must be separated by commas (',') and with only one '@' sign at the beginning of the whole message. This is on purpose, to force the Relay to parse them and check them one by one, as well as facilitate the parsing of the whole message coming from the in-world object.
- Everyone listening on the relay channel must have enough script memory available to handle a complete chat message which is limited to 1,000 character (2,000 bytes + processing).
- This meta-command is meant to make the Relay clear all the restrictions linked to the object issuing it. It is better to use it than to issue "counter-commands" to lift every restriction one by one without forgetting any.
- If the relay cancels an active session (for example because of a safe-word being called), it has to send a !release,ok message.
- When receiving this meta-command, the Relay must send a special acknowledgment that contains the version of the protocol it implements, on 4 digits. This number must be an integer, equal to the version of this specification, written just after the title on this very page, times 1000. For instance, "1.120" would translate to "1120". It makes it easier to compare versions without fear of losing information with a float cast to a string and back to a float.
- Do not mistake the version of the protocol with the version (@version) of the viewer or the version of the scripts (!implversion).
"ping" relay message and "!pong" object meta-command
- When logging on, the relay will reapply all the stored restrictions but it only makes sense if the in-world object is still around and available for use. It could have been reset, crashed, or used by someone else while the primary user was offline. To let the relay apply the restrictions would therefore make no sense. That's why the relay has to ask the object if it's still around and available, and if no appropriate answer is received in a timely fashion then it must lift all the restrictions issued by it before, in order to start fresh again. Notice that "ping" is a simple word (to stay consistent with "ok" and "ko") while "!pong" is a meta-command.
The relay message must be "ping,<object_uuid>,ping,ping" and the object message must be "ping,<user_uuid>,!pong". This allows the object to keep a listener open with a static filter, to reduce lag. <user_uuid> can be retrieved by a llGetOwnerKey() call.
The following meta-commands are deemed "optional" and relays do not have to implement them to be considered RLV compliant. They are mostly tied to the specific implementation of a particular relay that a particular furniture needs to interrogate. Furniture must not rely on the implementation of any of these commands to work.
- The relay implementation should identify itself with a string.
- The is just like !version with the exception that !implversion is not about the protocol but about the implementation of it and may contain a string
- This version string is not intended for automatic checks but to help debugging problems
- Think of this as the signature of that particular relay
- The string must not contain any "," or "!" characters
The following meta-commands used to belong to the specification without much discussion and without permission. They are mentioned here for reference but never meant to be part of the specification. They might be reinstated one day if they prove themselves useful, but at the time of this writing it would make no sense to consider a relay to be out of spec if it didn't implement those meta-commands.
- Syntax: !who/<UUID>
- This is an information message from the world telling the relay which avatar is controlling it.
- Traps that are automatically triggered by the victim should use the UUID of the victim instead of the person who has setup the trap, perhaps hours ago
- 00000000-0000-0000-0000-000000000000 (NULL_KEY) is valid, meaning an unknown avatar
- Note: The content of this message is obviously only as trustworthy as the world object is
Note from Marine : This meta-command may be designed to check the avatar operating the object to make griefing harder... but if this command is only as trustworthy as the object is, I don't see that as an improvement security-wise. Otherwise the idea has merit.
- Syntax: !handover/<key>/<type>
- Allows one world object to tell the relay it should accept commands from another world object instead without asking for permissions again. (For example a kidnapper object may do a force tpto to another sim where an other world object is waiting for the victim.
- (key) is the id of the target object
- (type) : 0 lift all restrictions of the source object, 1 keep them, it is the responsibility of the target object to keep them in mind
- The relay must ignore further "/"-parameters for future extension of the command.
- The relay must ignore any following commands on the same chat line
- The source object should send !handover/(key)/(type)|!release, so that relays that don't support !handover will release the restrictions and the next object can start normally.
- The relay must ensure with the !ping mechanism that the target object is available.
- The target object, however, might not be able to see the ping: For example because of a slow intersim teleport. In this case the target object must send a !pong on arrival of the victim anyway.
Note from Marine : This meta-command is particularly interesting, and is likely to go to the Other Meta-commands section in the future, or even in the main spec. It needs to be designed thoroughly first, though. I don't think it needs to be so complex, but it sure needs some checking.
- Send the exact @behav:option=param part in an llOwnerSay ()
- Retain a list of restrictions and their sources for when the user relogs.
- Force sit if unsit is prevented when relogging.
- When relogging, send a "ping" message (see above) to check the in-world object is still available. If no message after a few seconds (not necessarily a "!pong", any message aimed at the relay can do), release the rules linked to this object.
- The user must have access to the version of the specification and the version of the implementation (dialog, message, object name...) to check everything works correctly.
- The relay must silently ignore commands to remove non-existing restrictions without spamming the user with pointless "ask for permission dialogs"
- Relays must accept !release even if the world object is out of range.
- Relays must send a !release,ok if they cancel an active session (for example because of an safe-word)
- Relays must ensure that it preserves the order of commands. For example: If a restriction is queued in the Ask-dialog, the lifting of the restriction must not be executed immediately unless the pending restriction in the ask-dialog-queue is removed, too.
In-world object requirements
- If a world object sent any restrictions, it must end the session with !release even if the relay did not respond with "ok" unless all commands have been "ko"-ed. The relay may have delayed the execution to ask permission from the user who may confirm them after the session has already ended
- When receiving a "ping" message from a relay, reply with a "!pong" message (as described above) aimed at the avatar owning it, provided they are still restricted by the object.
- Never rely on an answer from the Relay, requests can be denied silently, the Relay can be unworn, the avatar can TP out or crash... => use timeouts.
- Don't poll the dataserver for online status, the Relay takes care of the relog part.
- World objects should not spam the relay channel. For example: Querying every minute the relay version of every person near although nobody shows any signs to actually use the object.
You can find the change history of this specification at Change History,