Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

SL Network Data Relay System

Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
11-27-2005 05:42
The SL-NDRS is a proof on concept system more than a final version. A while back, I talked about creating a system to relay data across multiple sims using relay shout nodes to create a network of sorts.


Well, here is the basic code.


A few things about this code:
1- It changes channels about once per 15 seconds, using a passkey to generate the channels psudo-randomly. This means the key should not be something that can be seen by random individuals.
2- It also uses a VERY THIN encryption on data being sent. Very. VERY VERY. It's just there to limit possible interferance, since the billions of possible channels will just about make this impossible to hack anyway.

What is the cost of this system in terms of data speed? Well. It takes under a second to send a data packet this way, so I would say it's pretty good.


Now for the downside of this system: If too much data is being relayed in a sim over chat, then messages will be lost. Not by the code, but by SL protecting itself and limiting the amount of chatter it will deal with.

What is the limit? Well, This system will do well for occasional traffic, such as sensor sweep requests or relaying data across a sim from a central point. It does not deal well with heavy loads of random chatter from dozens of sources. Perhaps extra code can be added in to keep track of chatter density, and force devices on the network to slow down transmission for a while?

Keep in mind, this code is for you to play with and tear apart for your own devices. It's not a complete toy in it's own right. Comments are added heavily to advise on what sections do. Questions are welcome.

Either way, here is the proof of concept code:


CODE
//Relay node listening script.  

//This script just listens and reshouts data to keep dataflow going. It is a seperate script, and relays data to another script if it hears anything directedat itself.

list lastmessages; //Records the last 100 IDs the system has seen, so that it can ignore them for future relays.
integer maxlast = 100; //the max for the above.

string codestring = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890-";
integer codelength;

string channel1 = "gfhgfhsdhdgh.";
string password = "test";

integer maxchannels = 2; //This is the number of listens to keeps track of data for each channel. About 4 time max channels the listens for each node.

integer last;
list handles1;


integer secondsdelay = 15;
integer clicks = 0;

//----------------------------------------------------------------------------------------------------------------------------------------------------------


integer pseudo_random(string text, integer nonce, integer start, integer end)
// This small code generates the random number from the string text given to it.
// The integer nonce is taken from the time.
{
return (integer)("0x"+llGetSubString(llMD5String(text, nonce), start, end));
}


string padded(string padstring)
// This code just pads a string up to 36 characters (key length) with spaces.
// This is to keep non-key data to the same length as a key do that it does not disrupt dataflow.
{
string padding = " ";
padstring = padstring + padding;
return llGetSubString(padstring, 0, 35);
}


string gencode(integer length)
// This system generates a random code of 'length' number of characters.
// With 37 characters ^ length, a 6 digit code gives 2,565,726,409 patterns.
// The codes are used to keep track of what messages have already been heard by relays.
{
codelength = llStringLength(codestring);
integer yff;
string code1 = "";
integer currentchar;
for (yff=0;yff<length;yff++)
{
currentchar = llFloor(llFrand(codelength));
code1 = code1 + llGetSubString(codestring, currentchar, currentchar);
}
return code1;
}

//----------------------------------------------------------------------------------------------------------------------------------------------------------

default
{
on_rez( integer param )
{
llResetScript();
}

state_entry()
{

}

touch_start(integer next)
{
integer xff;

for (xff=0;xff<99999;xff++)
{

clicks = xff;
string data = "This is test data string.";
string time = llGetTimestamp();
data = llXorBase64Strings(llStringToBase64(data + " - " + (string)clicks), llStringToBase64(password));

// This code determins the channel to send the data on.
integer next1 = pseudo_random(channel1, secondsdelay * ((integer)llGetGMTclock() / secondsdelay), 0, 7);

// This code sends the data to the nodes within listening range.
// 0-5 random ID code | 6-41 source key | 42-76 destination key or item | 77-78 lifespan | 79-253 (175 characters max data)

llShout(next1, gencode(6) + (string)llGetKey() + padded("ALLDEVICES") + "99" + data);

}

}

}



CODE
//Relay node listening script.  

//This script just listens and reshouts data to keep dataflow going. It is a seperate script, and relays data to another script if it hears anything directedat itself.

list lastmessages; //Records the last 100 IDs the system has seen, so that it can ignore them for future relays.
integer maxlast = 100; //the max for the above.

string channel1 = "gfhgfhsdhdgh.";

string password = "test";

string channelx = channel1;

integer maxchannels = 2; //This is the number of listens to keeps track of data for each channel. About 4 time max channels the listens for each node.
integer last1;

list handles1;

integer secondsdelay = 15;
integer verbose = 0; // This allows you to watch the system and make sure it's working properly.

integer next;

// Globals
integer iNotecardIndex;
integer iNotecardCount;
integer iNoteCardLine;
key kCurrentDataRequest;
string sSettingsNotecard;

//----------------------------------------------------------------------------------------------------------------------------------------------------------

integer pseudo_random(string text, integer nonce, integer start, integer end)
// This small code generates the random number from the string text given to it.
// The integer nonce is taken from the time.
{
return (integer)("0x"+llGetSubString(llMD5String(text, nonce), start, end));
}

//----------------------------------------------------------------------------------------------------------------------------------------------------------

integer StringLeftICompare( string sLeftMatch, string sLongString )
{
integer iLength;

iLength = llStringLength( sLeftMatch ) - 1;
if( llToLower(llGetSubString( sLongString, 0, iLength ) ) == llToLower(sLeftMatch) )
return( TRUE );
return( FALSE );
}


string GetValue( string sString )
{
integer iStart;
string sValue = "";
string sbValue = "";

iStart = llSubStringIndex( sString, "=" ) + 1;
if( iStart )
{
sValue = llGetSubString( sString, iStart, llStringLength(sString) - 1 );
if( sValue )
{
sbValue = llToLower( sValue );
if( sbValue == "true" )
sValue = "1";
if( sbValue == "false" )
sValue = "0";
return( sValue );
}
}
return( NULL_KEY );
}

default
// This code is so you can change the default settings above to ones in a notecard. This code is NOT needed for this system to work.
{
on_rez( integer param )
{
llResetScript();
}

state_entry()
{
integer iii;

llSetText( "Initializing...", <0,0.5,0>, 1.0 );

iNotecardCount = llGetInventoryNumber( INVENTORY_NOTECARD );
iNotecardIndex = 0;
if( iNotecardCount > iNotecardIndex )
{
sSettingsNotecard = llGetInventoryName( INVENTORY_NOTECARD, iNotecardIndex );
iNoteCardLine = 0;
kCurrentDataRequest = llGetNotecardLine( sSettingsNotecard, iNoteCardLine );
iNotecardIndex++;
}
if( iNotecardIndex == 0 )
{
llWhisper( 0, "Using Default Values." );
state run_object;
}
}

dataserver( key kQuery, string sData )
{
list lSetting;

kCurrentDataRequest = "";
if( sData != EOF )
{
// you can string several of these tests for whatever values you may want.

if( StringLeftICompare( "channel1=", sData ) ){
llMessageLinked(LINK_SET, 0, "channel1=" + (string)GetValue( sData ), "");
channel1 = (string)GetValue( sData );}
else if( StringLeftICompare( "password=", sData ) ){
llMessageLinked(LINK_SET, 0, "password="+(string)GetValue( sData ), "");
password = (string)GetValue( sData );}
else if( StringLeftICompare( "maxchannels=", sData ) ){
llMessageLinked(LINK_SET, 0, "maxchannels="+(string)GetValue( sData ), "");
maxchannels = (integer)GetValue( sData );}
else if( StringLeftICompare( "secondsdelay=", sData ) ){
llMessageLinked(LINK_SET, 0, "secondsdelay="+(string)GetValue( sData ), "");
secondsdelay = (integer)GetValue( sData );}
else if( StringLeftICompare( "verbose=", sData ) ){
llMessageLinked(LINK_SET, 0, "verbose="+(string)GetValue( sData ), "");
verbose = (integer)GetValue( sData );}


kCurrentDataRequest = llGetNotecardLine( sSettingsNotecard, ++iNoteCardLine );
}
else
{
iNotecardIndex++;
if( iNotecardIndex < llGetInventoryNumber( INVENTORY_NOTECARD ) )
{
sSettingsNotecard = llGetInventoryName( INVENTORY_NOTECARD, iNotecardIndex );

iNoteCardLine = 0;
llGetNotecardLine( sSettingsNotecard, iNoteCardLine );
}
else
{
state run_object;
}
}
}
}

state run_object
// This is the actual listening and relay code for the system. It listens for chat on the psudo-random channel
// and if it's lifespan is greater than 00 AND it has not heard it before (using 6 starting 6 digit code) relays it.
{
on_rez( integer param )
{
llResetScript();
}

state_entry()
{
string channelx = channel1;
llSetText( "", <1,1,1>, 1.0 );
integer now = secondsdelay * ((integer)llGetGMTclock() / secondsdelay);
integer count = 0;

while(count < maxchannels)
//Number of channels to hold open at any one time.
//If you have problems with missed message due to a lagged sim increase the maxchannels number.
{
handles1 += llListen(pseudo_random(channelx, (last1 = now), 0, 7), "", "", "");
now += secondsdelay;
++count;
}
llSetTimerEvent(5); //this is so if there is a nasty time dilation
}


state_exit()
{
handles1 = [];

}


timer()
{
//This code updates the chat channels used about every 'secondsdelay' seconds. This version, unlike the one in
//The wiki, will continue to update after a 24 hour cycle has ended. Minor debug, but hard to catch.

next = secondsdelay * ((integer)llGetGMTclock() / secondsdelay) + secondsdelay;

if(next > last1 || next < last1 - 30)
{
last1 = next;
llListenRemove(llList2Integer(handles1, 0));
handles1 = llList2List(handles1, 1, -1) + [llListen(pseudo_random(channelx, secondsdelay * ((integer)llGetGMTclock() / secondsdelay), 0, 7), "", "", "")];
if (llGetListLength(handles1) > maxchannels)
{
llListenRemove(llList2Integer(handles1, 0));
}
if (verbose == 1)
llWhisper(0,"If event happened." + (string)next + " " + (string)last1);


}
if (verbose == 1)
llWhisper(0,"Timer event happened." + (string)next + " " + (string)last1);
llSetTimerEvent(5); //this is so if there is a nasty time dilation
}


listen(integer a, string b, key c, string data)
// If this is invoked, then it has heard a shout from a relay node or shouter.
{
string currentid = llGetSubString(data, 0, 5);
if (llListFindList(lastmessages, [currentid]) == -1)
// This code looks to see if it's seen this message before from the 6 digit random header code.
// If the code IS found, then it will do nothing.
{
lastmessages = lastmessages + [currentid];
if (llGetListLength(lastmessages) > maxlast)
//Since it has not heard this code before, it adds it to it's list of heard codes, and if the number of
//Known codes it greater than 'maxlast', then it removes the oldest one.
{
lastmessages = llDeleteSubList(lastmessages, 0, 1);
}

// This code looks to see if the message lifespan has run out. If so, then it does not relay it.
// If it can relay it, it relays the message with the lifespan reduced by one.
integer lifespan = (integer)llGetSubString(data, 77, 78);
if ( lifespan != 0)
{
if (lifespan < 10) {
// Check to see if the lifespan needs to be padded with a zero in front of it.
data = llGetSubString(data, 0, 76) + "0" + (string)( lifespan - 1 ) + llGetSubString(data, 79, -1);
} else {
data = llGetSubString(data, 0, 76) + (string)( lifespan - 1 ) + llGetSubString(data, 79, -1);
}
llShout(pseudo_random(channelx, secondsdelay * ((integer)llGetGMTclock() / secondsdelay), 0, 7), data);
}
// Get and decode the data.
string datastring = llGetSubString(data, 79, -1);

data = llBase64ToString(llXorBase64Strings(datastring , llStringToBase64(password)));
if (verbose == 1)
llWhisper(0,"Node heard: " + datastring + " on channel " + (string)a + " with the header " + currentid );
}
}

}
Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
11-27-2005 09:17
Oh, I just realized the above is missinga few semi-colins. I'll try and fix it tonight.

Overall, the code is solid...
Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
11-28-2005 02:51
Ok. small typo bugs fixed. Sorry for the glitch.