Don't click here unless you want to be banned.

LSL Wiki : exchangeTLTP

HomePage :: PageIndex :: RecentChanges :: RecentlyCommented :: UserSettings :: You are crawl338.us.archive.org

Touch-Link Transfer Protocol (TLTP for short)


Change Log:
Version 0.33e

Main changes since 0.32a
Support for TLML-L pages, re-added scanned in the background.

Main changes since 0.31
- support for TLML-Light pages

Main changes from 0.25:
- types of data transmitted are now described by the first character sent
- types of data: animation, sound, TLML, URL, XTMP, notecard key, chat request, RPC
- TLTP codes migrated to TLML
- TLTP-RPC
- URL format revised

Main changes from 0.2:
- codes for XTMP
- code for remote animation playing
- code for remote sound playing
- added stub for future TLTP-RPC

Main changes from 0.1:
- both modes of communication are now stateless and connectionless, although the TLTP-over-chat method is still filtered on the server->client way, by channel number
- chat-capable servers now advertise on -9, instead of clients advertising
- new -06 error code for server-initiated redirections

Return to protocol exchange
Description of TLML
Sample server and client code

What is this ?

TLTP is a protocol between a HUD attachment or stand-alone object acting as a programmable interface and an object either local or remote for exchanging data.
The data sent can be TLML-formatted prim-pages that can link to another TLML page, effectively making this a kind of HTTP-for-SL. It can also be chat commands, link messages to send, animation or sounds to play, etc... enabling it to serve as transport layer for anything in SL.

TLTP

The protocol supports three modes for the transfer of the TLML data: chat functions or llEmail functions for a direct transmission of the data, or the dataserver event for reading the data from a notecard.

The chat way:
Service Discovery:
The server advertises itself on channel -9, saying its default URL (containing the server's channel number, see URL format below). The client listens for this and stores it temporarily.

Browsing
The client then just has to open a listener on a random channel to request a page, by saying an URL composed of the index of the page requested, the chat communication method identifier "0" and the number of the random channel on which to send the page, on the server's channel. This URL can also include the name of the client.
Example:
- Server advertises the URL "|0|-671|Bob's Server|homepage", which stands for "feel free to browse Bob's Server on private channel -671, starting at the prim-page of index 'homepage' ".
- Client says "U|homepage|0|76711|My browser" on channel -671 to request the default page.
- Server then sends the TLML page on channel 76711 so that only the client that requested this page will receive it.


To send the prim-page back, the server simply says the TLML commands composing this page, on the same client-specified channel.
Example continued:
- Server says "T/0/U!nextpage!1!66864f3c-e095-d9c8-058d-d6575e6ed1b8/8201F/<1,1,0>/<0.25,0.25,0.01>/<0.75,0.75,0.01>" on channel 76711, which is TLML code for a centered opaque yellow rectangle covering half the screen, that links to the URL "U!nextpage!1!66864f3c-e095-d9c8-058d-d6575e6ed1b8" when clicked.


The client then usually extracts the string that trails the starting "T/0" to its first child prim, which interprets it and changes appearance according to the parameters it contains.



The email way:
Service Discovery:
There is no automatic discovery method. However browser scripts should support bookmarks, and chat servers can redirect to email servers.
This leaves some space for developping a DNS-like service.

Browsing:
The client simply sends an email of subject "TLTP" to the server and message containing an URL composed of the index of the requested page, the communication method identifier "1" and its key (and optionnally its name), and the server replies with a stream of emails of subject "TLTP" and of message containing one or more TLML command line(s) for this page. Because of the slow speed of this transfer method, it's best to limit it to simple pages of 2 or 3 prims.



URL Format:
All URLs should be strings starting with "U" when sent over TLTP, followed by a TightList composed of:
string Index of the page
integer cast as string Optional. Communication method: 0 for chat, 1 for email This will support more methods in the future.
key or integer (cast as string) Optional, set only if previous field is set. Key of the server for email, or server's channel for chat
string Optional, set only if previous two fields are set. Name of the server
Example URLs: U!bestiary (when passed, the browser will request the page "bestiary" to the last called server)
U!Forward!0!128 (the browser will ask for the page "Forward" on channel 128, this can be used to make remote controllers easily)
U!Store!1!66864f3c-e095-d9c8-058d-d6575e6ed1b8!Jesrad Seraph.The Bestiary (Points at the page "Store" on server The Bestiary owned by Jesrad Seraph, of key 66864f3c-e095-d9c8-058d-d6575e6ed1b8, via email)


URL Type Method Format
Chat 0 Index, 0, channel [, server_name]]
Email 1 Index, 1, address [, server_name]
Joint notecard 2 Index, 2 [, line, name]
The name of the server that can be added to URLs is meant to be used in Bookmarks and for future systematic resolution of names ;)



Data types:

TLTP Servers and clients are not limited to exchanging URLs and TLML data, they can send any of these types of data:
First character Data Description
U TightList URL see section above
T string TLML command
t notecard_key key of a notecard containing data This is just a page stored in a notecard, the entire card will be read
x string XTM command
X string XTMP command
J TightList([begin, end, offset, (visible-begin, (visible-end))]) all integers, last 2 optional, can only be called from within a TLML-L page
C TightList([(channel,) text]) channel is optional, if left out, llSay is used otherwise it uses llShout
c string uses llOwnerSay to say string
s stops all sounds playing
S TightList([sound(, volume)]) llTriggerSound(sound, volume) sound can be a name (but must be in browsers inventory) or key, volume is optional, defaults to 0.5
L TightList([sound(, volume)]) llPlaySound(sound, volume) sound can be a name (but must be in browsers inventory) or key, volume is optional, defaults to 0.5
l
llToLower("L")
TightList([sound(, volume)]) llLoopSound(sound, volume) sound can be a name (but must be in browsers inventory) or key, volume is optional, defaults to 0.5
D TightList([start(, end)]) both integers, end is optional, if left out only start is cleared.
R TightList([scriptname] + parameters ) composed of the name of the RPC script, and the necessary parameters
N TightList([difference, notecard_texture_key, subgroup]) difference is how far and direction to move the current position on the viewers that corespond to notecard_texture_key with regaurds to subgroup
W float duration to wait before interpreting further commands Useful for mimicking Flash ;)
K no idea Reserved for keys
k no idea Reserved for keys
p integer Request permissions, discard existing permissions
P integer Request permissions, keep existing permissions
A string name of an animation to play llStartAnimation Use the name for built-in animations
a string name or key of animation to stop llStopAnimation
M TightList TightList of multiple TLTP commands chained together

Sample Code


TLTP Server:
Works
// TLTP Server
// version 0.33e
// Author: Jesrad Seraph
// Modify and redistribute freely as long as you allow free modification and redistribution

// This example server supports both chat and email transport methods
// The pages are sent as keys for server performance

// Currently, the default page is the first page cached.

// TODO: support crossing the transport methods (listen->email, email->shout)

integer cur_emailer = 0;
integer max_emailer;

string indexname;        // contains the index page name

integer channel = 777;        // private server channel for accepting requests
integer handle;            // listener handle for getting requests
float advert_rate = 5.0;    // timerate for announcing the server
string tltp_str = "TLTP";    // saving memory
//string eof = EOF;
key ind;
integer index;

list TLML_L;


// courtesy of Strife Onizuka
list TightListParse(string a)
{
    string b = llGetSubString(a,0,0);//save memory
    return llParseStringKeepNulls(llDeleteSubString(a,0,0), [a=b],[]);
}
string TightListDump(list a, string b)
{
    string c = (string)a;
    if(llStringLength(b)==1)
        if(llSubStringIndex(c,b) == -1)
            jump end;
    integer d = -llStringLength(b += "|\\/?!@#$%^&*()_=:;~{}[],\n\" qQxXzZ");
    while(1+llSubStringIndex(c,llGetSubString(b,d,d)) && d)
        ++d;
    b = llGetSubString(b,d,d);
    @end;
    c = "";//save memory
    return b + llDumpList2String(a, b);
}

sendEmail(string a, string s, string m)
{
    if (max_emailer > 0)
    {
        llMessageLinked(LINK_THIS, cur_emailer, TightListDump([a + "@lsl.secondlife.com", s, m], "!"), "");
        cur_emailer = (cur_emailer + 1) % max_emailer;
    } else llEmail(a + "@lsl.secondlife.com", s, m);
}

default
{
    state_entry()
    {
        if (llGetInventoryNumber(INVENTORY_NOTECARD) <= 0)
        {
            llOwnerSay("No pages found.");
        } else state ready;
    }

    changed(integer c)
    {
        if (c & CHANGED_INVENTORY) { llSleep(1.0); llResetScript(); }
    }
}

state ready
{    
    state_entry()
    {
        index = llGetListLength(TLML_L);
        while(index)
            if(llGetInventoryType(llList2String(TLML_L, --index)) == INVENTORY_NONE)
                TLML_L = llDeleteSubList(TLML_L,index,index);
        indexname = llGetInventoryName(INVENTORY_NOTECARD, index = 0);
        integer a = llGetInventoryNumber(INVENTORY_NOTECARD);
        while(index < a)
            if(llListFindList(TLML_L, [llGetInventoryName(INVENTORY_NOTECARD, index)]) + 1)
                ++index;
            else
            {
                ind = llGetNotecardLine(llGetInventoryName(INVENTORY_NOTECARD, index), 0);
                a = index;
            }
        max_emailer = llGetInventoryNumber(INVENTORY_SCRIPT) - 1;
        handle = llListen(channel, "", "", "");
        llOwnerSay("Server ready on channel " + (string)channel);
        llSetTimerEvent(advert_rate);
        llOwnerSay((string)llGetKey() + "@lsl.secondlife.com");
        if (llGetObjectDesc() == "") llSetObjectDesc("Server");
    }

    dataserver(key queryid, string data)
    {
        if(ind == queryid)
        {
            if(llList2String(TightListParse(data),-1) == "TLML-L")
                if(llListFindList(TLML_L, [data = llGetInventoryName(INVENTORY_NOTECARD, index)]) == -1)
                    TLML_L += data;
            if(++index < llGetInventoryNumber(INVENTORY_NOTECARD))
                ind = llGetNotecardLine(llGetInventoryName(INVENTORY_NOTECARD, index), 0);
        }
    }

    changed(integer c)
    {
        if (c & CHANGED_INVENTORY) { llSleep(1.0); state default; }
    }

    timer()
    {
        llShout(-9, "U" + TightListDump([indexname, 0, channel, llGetObjectDesc()], "#") );
        llGetNextEmail("", "");
    }

    listen(integer ch, string n, key id, string msg)
    {
        string a = llGetSubString(msg, 0, 0);
        if (a != "U") return;
        list info = TightListParse(llDeleteSubString(msg, 0, 0));
        if (llGetListLength(info) < 3) return;    // simple sanity check

        string req = llList2String(info, 0);
        key thepage = llGetInventoryKey(req);
        string m = llList2String(info, 1);
        integer c = (integer)llList2String(info, 2);

        if (m != "0") return;    // not supporting cross transport methods right now

        if (thepage == NULL_KEY)
        {
            llShout(c, "T" + TightListDump(["-4", indexname], "!"));
        } else {
            integer type = llGetInventoryType(req);
            if (type + 1)
            {
                if (type == INVENTORY_NOTECARD) {
                    if(llListFindList(TLML_L, [req]) + 1)
                        req = "U" + TightListDump([(string)thepage, 2, 0, llGetKey()], "!");
                    else
                        req = "t" + (string)thepage;
                    llShout(c, req);
                } else
                if (type == INVENTORY_ANIMATION) { llShout(c, "A" + (string)thepage); } else
                if (type == INVENTORY_SOUND) { llShout(c, "S" + TightListDump([(string)thepage, "1.0"], "!")); } else
                if (type == INVENTORY_SCRIPT) { llShout(c, "T" + TightListDump(["-4", indexname], "!")); } else
                {
                    llShout(c, "cDownloading " + req);
                    if((n = llGetOwnerKey(id)) != id)
                        llGiveInventory(n, req);
                    else
                    {
                        llShout(c, "cCannot resolve your key ! Please come to sim " + llGetRegionName());
                    }
                }
            }
            else
            {
                llShout(c, "T" + TightListDump(["-4", indexname], "!"));
            }
        }
    }

    email(string time, string address, string subj, string message, integer left)
    {
        if (subj == tltp_str)
        {
            integer start = llSubStringIndex(message, "\n\n") + 2;
            string a = llGetSubString(message, start, start);
            if (a != "U") return;

            list info = TightListParse(llDeleteSubString(message, 0, start));
            string req = llList2String(info, 0);
            key thepage = llGetInventoryKey(req);
            string m = llList2String(info, 1);
            if (m != "1") return;    // not supporting cross transport methods right now

            if (thepage == NULL_KEY)
            {
            } else {
                integer type = llGetInventoryType(req);
                if (type + 1)
                {
                    // page is not a notecard
                    if (type == INVENTORY_NOTECARD) {
                        if(llListFindList(TLML_L, [req]) + 1)
                            req = "U" + TightListDump([(string)thepage, 2, 0, llGetKey()], "!");
                        else
                            req = "t" + (string)thepage;
                        sendEmail(llList2String(info, 2), tltp_str, req); } else
                    if (type == INVENTORY_ANIMATION) { sendEmail(llList2String(info, 2), tltp_str, "A" + indexname); } else
                    if (type == INVENTORY_SOUND) { sendEmail(llList2String(info, 2), tltp_str, "S" + TightListDump([thepage, "1.0"], "!")); } else
                    if (type == INVENTORY_SCRIPT) { sendEmail(llList2String(info, 2), tltp_str, "T" + TightListDump(["-4", indexname], "!")); } else
                    {
                        sendEmail(llList2String(info, 2), tltp_str, "cDownloading " + req);
                        string id = llGetOwnerKey(llGetSubString(message, 0, 35));
                        if (id != llGetSubString(message, 0, 35)) {
                            llGiveInventory(id, req);
                        } else sendEmail(llList2String(info, 2), tltp_str, "cCannot resolve your key ! Please come to sim " + llGetRegionName());
                    }
                }
                else
                {
                    sendEmail(llList2String(info, 2), tltp_str, "T!-4");
                }
            }
        } else llOwnerSay("Unknown email received: " + subj + ": " + message);
    }
}

Sample RPC plugin (call it MoveTo, use by sending R!MoveTo!<vector>[!time] to a browser that contains this script):
// MoveTo TLTP Plugin
// Sample plugin for RPC demonstration

integer rpc_code = 4400;

list TightListParse(string a)
{
    string b = llGetSubString(a,0,0);//save memory
    return llParseStringKeepNulls(llDeleteSubString(a,0,0), [a=b],[]);
}

default
{
    link_message(integer part, integer code, string msg, key id)
    {
        if (code != rpc_code) return;
        if (msg != llGetScriptName()) return;

        list l = TightListParse((string)id);
        float delta;

        if (llGetListLength(l) > 1) { delta = (float)llList2String(l, 1); } else
            delta = 0.5;

        vector target = (vector)llList2String(l, 0);

        if (target == ZERO_VECTOR)
        {
            llStopMoveToTarget();
        } else llMoveToTarget(target, delta);
    }
}
There is one comment on this page. [Display comments/form]