Recommendation: GSI CLEP (v1)
Structured RPC protocol for chat and link messages that adds region-, prim-, and script-scope targeting; channel-separated domains; and cross-border routing support
The Global Scripting Institute (GSI) is an informal organization of Second Life® users that design and test standards for efficient, flexible, and readable scripts in Second Life. "Second Life®" and "Second Life Grid™" are trademarks of Linden Research, Inc., d/b/a Linden Lab. The Global Scripting Institute and its catalog are not affiliated with or sponsored by Linden Research.
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
The Chat/Link_message Encoding Protocol (CLEP) is a protocol for flexible and targeted inter-script communication in Lua and Linden Scripting Language (LSL).
Lua-style syntax is used throughout this document, but all specifications also apply to the equivalent LSL functions and event callbacks.
CLEP defines a structured JSON message format sent over chat, link message, HTTP, or email. CLEP also defines a standardized method for converting strings into hashed negative integer channels for chat listeners. CLEP defines these channels as domains. It is RECOMMENDED to use unique domains wherever possible, such as prim UUIDs, to optimize server-side channel separation (see FAQ for details).
An outgoing CLEP message MUST be a JSON object with the following parameters:
| Key | Required? | Type | Value |
|---|---|---|---|
domain |
REQUIRED | string | MUST be any string signifying the CLEP domain. Listeners SHOULD drop messages that do not match a domain they are listening to. |
id |
REQUIRED | uuid cast as JSON string | MUST be ll.GenerateKey() or some other unique UUID. |
method |
REQUIRED | {string} | MUST be an array of any strings signifying a specific operation, such as {"File", "Write"}. Methods are canonically described using dot notation, such as File.Write. |
source -> region |
RECOMMENDED | string | If included, MUST be ll.GetRegionName(). If omitted, message SHALL NOT be forwarded via cross-region relays. |
source -> prim |
RECOMMENDED | uuid cast as JSON string | If included, MUST be ll.GetKey(). If omitted, message SHALL NOT be forwarded via any relays. |
source -> script |
RECOMMENDED | string | If included, MUST be ll.GetScriptName(). If omitted, message SHALL NOT be responded to. |
target -> region |
OPTIONAL | string | If included, MUST be the name of the region in which the targeted script(s) exist. If omitted, message SHALL NOT be target-routed via cross-region relays. |
target -> prim |
OPTIONAL | uuid cast as JSON string | If included, MUST be the uuid of the prim in which the targeted script(s) exist. If omitted, message SHALL NOT be forwarded via any relays. |
target -> root |
OPTIONAL | uuid cast as JSON string | If included, MUST be the uuid of the root prim of the linkset in which the targeted script(s) exist. SHOULD NOT be used in conjunction with target -> prim. |
target -> link |
OPTIONAL | number | If included, MUST be the link number or LINK_* constant targeting the prim(s) of the same linkset in which the targeted script(s) exist. MUST NOT be used in conjunction with target -> prim. |
target -> script |
OPTIONAL | string | If included, MUST be the name of the targeted script(s). |
utime |
OPTIONAL | os.time() in Lua llGetUnixTime() in LSL |
If included, MUST be the Unix time when the message was marshalled. If this message is to be sent with SNEP encapsulation, which includes its own timestamp, this field SHOULD NOT be included. |
params |
OPTIONAL | any | MAY be any valid JSON, such as a string, object, et cetera. |
result |
OPTIONAL | any | MAY be any valid JSON. If this message is a request (not a response), this pair MUST BE omitted. |
Note that LSL scripts will report a negative timestamp starting January 19, 2038 because LSL integers are 32-bit. There is currently no workaround for this in LSL. Lua scripts checking timestamps may need to compensate for Y2038-affected timestamps from LSL scripts, such as when verifying SNEP signatures.
The CLEP Channel Hash Utility Function (CHUF) generates an number (integer) channel based on a string domain. This facilitates CLEP's channel separation, which reduces the number of events triggered when a large collection of objects is broadcasting messages to each other on a common hard-coded channel integer.
A CLEP CHUF MUST be defined as bit32.bor(ll.Hash(domain), 0x80000000) in Lua, or (llHash(domain) | 0x80000000) in LSL.
This accomplishes the following:
domain string, which is returned as a 32-bit signed integer number in both Lua and LSL.| Placeholder | Type | Description |
|---|---|---|
domain |
string | MUST be a string of any value. This value can be treated as an arbitrary "string channel". By separating CLEP traffic in each domain to a different integer channel, CLEP messages from other domains are filtered server-side - messages not sent to the same domain will not trigger a listen event in the first place.A pre-shared string, the prim's UUID, another prim's UUID, or some combination of the three, is RECOMMENDED. |
Note that the Channel Hash Utility Function is not designed to be secure or private. The SDBM algorithm is not cryptographically safe and collisions are extremely rare, but possible given the resulting 31-bit channel space.
Why are channels hashed? Isn't it better to have a pre-shared channel?
Per the Second Life Wiki, the Second Life server processes every chat message by first checking that it matches the channel of any open listens in the region, then performs some additional checks (self-chat, distance, other filters). If - and only if - those checks pass, a listen event is added to the script's event queue.
CLEP enforces channel separation using hashing to take advantage of the performance gains offered by this check. Traditionally, LSL scripts use a pre-shared channel integer for a certain product. If a shared channel is used, any broadcasts that need to be done via llRegionSay, llSay, llShout, or llWhisper are sent to all scripts listening to that channel.
Advanced networks of scripts tend to use multiple channels to offset this. However, sharing multiple integers as channel numbers is complex, and the integers don't themselves have any substantial intrinsic meaning.
CLEP does something similar, except that the channel numbers are deterministically pseudo-random by use of a hash function. That way, instead of randomly coming up with a channel number, CLEP channels are effectively defined as strings and the actual integer channel used internally is deterministic but irrelevant.
As a result, CLEP encourages the use of multiple channels (via the domain value) for complex scripts, which subtly enforces channel separation and reduces script impact overall by reducing unnecessary listen events.
CLEP was authored by Nelson Jenkins on behalf of GSI.