MODULE UDPChatBase;
IMPORT
Kernel, Strings, BIT, IO := Streams, IP, FS := Files;
CONST
serverPort* = 14000;
UserFile = "Sage.UDPChatUsers.dat";
clientKeepAliveInterval* = 20000;
clientKeepAliveAwait* = clientKeepAliveInterval * 3 + clientKeepAliveInterval DIV 2;
UDPHdrLen = 8;
MaxUDPDataLen* = 10000H - UDPHdrLen;
VERSION* = 0002H;
ACK* = 000AH;
SEND_MESSAGE* = 010EH;
LOGIN* = 03E8H;
CONTACT_LIST* = 0406H;
SEARCH_UIN* = 041AH;
SEARCH_USER* = 0424H;
KEEP_ALIVE* = 042EH;
SEND_TEXT_CODE* = 0438H;
LOGIN_1* = 044CH;
INFO_REQ* = 0460H;
EXT_INFO_REQ* = 046AH;
CHANGE_PASSWORD* = 049CH;
STATUS_CHANGE* = 04D8H;
LOGIN_2* = 0528H;
UPDATE_INFO* = 050AH;
UPDATE_EXT_INFO* = 04B0H;
ADD_TO_LIST* = 053CH;
REQ_ADD_TO_LIST* = 0456H;
QUERY_SERVERS* = 04BAH;
QUERY_ADDONS* = 04C4H;
NEW_USER_1* = 04ECH;
NEW_USER_REG* = 03FCH;
NEW_USER_INFO* = 04A6H;
CMD_X1* = 0442H;
MSG_TO_NEW_USER* = 0456H;
LOGIN_REPLY* = 005AH;
USER_ONLINE* = 006EH;
USER_OFFLINE* = 0078H;
USER_FOUND* = 008CH;
RECEIVE_MESSAGE* = 00DCH;
END_OF_SEARCH* = 00A0H;
INFO_REPLY* = 0118H;
EXT_INFO_REPLY* = 0122H;
STATUS_UPDATE* = 01A4H;
REPLY_X1* = 021CH;
REPLY_X2* = 00E6H;
UPDATE_REPLY* = 01E0H;
UPDATE_EXT_REPLY* = 00C8H;
NEW_USER_UIN* = 0046H;
NEW_USER_REPLY* = 00B4H;
QUERY_REPLY* = 0082H;
SYSTEM_MESSAGE* = 01C2H;
MESSAGE_TYPE_NORMAL* = 0001H;
MESSAGE_TYPE_URL* = 0004H;
MESSAGE_TYPE_DATA* = 0008H;
TYPE
String = Strings.String;
ACKRec* = POINTER TO RECORD
seqNum*: INTEGER;
END;
Client* = OBJECT
VAR
ip*: IP.Adr;
port*: LONGINT;
inSeqNum*, outSeqNum*: INTEGER;
uin*: LONGINT;
keepAliveTimer*: Kernel.MilliTimer;
ACKList-: List;
PROCEDURE &New*;
BEGIN
NEW (ACKList);
END New;
PROCEDURE Finalize*;
BEGIN
ACKList.Clear;
END Finalize;
END Client;
UserInfo* = POINTER TO RECORD
uin*: LONGINT;
shortName*, fullName*, eMail*: ARRAY 65 OF CHAR;
END;
User* = POINTER TO RECORD (UserInfo)
password*: LONGINT;
END;
Users* = OBJECT
VAR
list: List;
lastUIN: LONGINT;
PROCEDURE &New*;
BEGIN
NEW (list);
lastUIN := 1000;
Load;
END New;
PROCEDURE Load;
VAR
u: User;
f: FS.File;
r: FS.Reader;
BEGIN
f := FS.Old (UserFile);
IF f # NIL THEN
FS.OpenReader (r, f, 0);
WHILE r.res = IO.Ok DO
NEW (u);
r.RawLInt (u.uin);
r.RawLInt (u.password);
r.RawString (u.shortName);
r.RawString (u.fullName);
r.RawString (u.eMail);
IF r.res = IO.Ok THEN
IF u.uin > lastUIN THEN
lastUIN := u.uin
END;
list.Add (u);
END;
END;
END;
END Load;
PROCEDURE Store*;
VAR
f: FS.File; w: FS.Writer;
i: LONGINT;
u: User;
ptr: ANY;
BEGIN
IF list.GetCount () > 0 THEN
f := FS.New (UserFile);
IF (f # NIL) THEN
FS.OpenWriter(w, f, 0);
i := 0;
WHILE (w.res = IO.Ok) & (i < list.GetCount ()) DO
ptr := list.GetItem (i);
u := ptr (User);
w.RawLInt(u.uin);
w.RawLInt(u.password);
w.RawString(u.shortName);
w.RawString(u.fullName);
w.RawString(u.eMail);
INC (i);
END;
IF w.res = IO.Ok THEN
w.Update;
FS.Register (f)
END
END
END
END Store;
PROCEDURE Add* (password, shortName, fullName, eMail: String): User;
VAR
u: User;
BEGIN
NEW (u);
INC (lastUIN);
u.uin := lastUIN;
u.password := Code (password^);
COPY (shortName^, u.shortName);
COPY (fullName^, u.fullName);
COPY (eMail^, u.eMail);
list.Add (u);
RETURN u;
END Add;
PROCEDURE Find* (uin: LONGINT): User;
VAR
i: LONGINT;
u: User;
ptr: ANY;
BEGIN
i := 0;
WHILE i < list.GetCount () DO
ptr := list.GetItem (i);
u := ptr (User);
IF uin = u.uin THEN
RETURN u;
END;
INC (i);
END;
RETURN NIL;
END Find;
PROCEDURE PasswordCorrect* (uin: LONGINT; password: String): BOOLEAN;
VAR
u: User;
BEGIN
u := Find (uin);
IF u # NIL THEN
IF Code (password^) = u.password THEN
RETURN TRUE;
END;
END;
RETURN FALSE;
END PasswordCorrect;
END Users;
Buffer* = OBJECT (Strings.Buffer)
PROCEDURE AddInt* (n, len: LONGINT);
VAR
i: INTEGER;
b, res: LONGINT;
s: ARRAY 4 OF CHAR;
BEGIN
ASSERT (len <= 4);
i := 0; b := 1;
WHILE i < len DO
s[i] := CHR (BIT.LAND ((n DIV b), 0FFH));
b := b * 100H;
INC (i);
END;
Add (s, 0, len, TRUE, res)
END AddInt;
END Buffer;
PArray = POINTER TO ARRAY OF ANY;
List* = OBJECT
VAR
list: PArray;
count: LONGINT;
readLock: LONGINT;
PROCEDURE &New*;
BEGIN
NEW (list, 8); readLock := 0
END New;
PROCEDURE GetCount*() : LONGINT;
BEGIN
RETURN count
END GetCount;
PROCEDURE Grow;
VAR
old: PArray;
i: LONGINT;
BEGIN
old := list;
NEW (list, LEN(list) * 2);
FOR i := 0 TO count - 1 DO list[i] := old[i] END;
END Grow;
PROCEDURE Add*(x : ANY);
BEGIN {EXCLUSIVE}
AWAIT (readLock = 0);
IF count = LEN (list) THEN Grow END;
list[count] := x;
INC (count)
END Add;
PROCEDURE IndexOf * (x : ANY) : LONGINT;
VAR
i: LONGINT;
BEGIN
i := 0 ; WHILE i < count DO IF list[i] = x THEN RETURN i END; INC(i) END;
RETURN -1
END IndexOf;
PROCEDURE Remove* (x : ANY);
VAR
i: LONGINT;
BEGIN {EXCLUSIVE}
AWAIT (readLock = 0);
i:=0; WHILE (i < count) & (list[i] # x) DO INC(i) END;
IF i < count THEN
WHILE (i < count - 1) DO list[i] := list[i + 1]; INC(i) END;
DEC(count);
list[count] := NIL
END
END Remove;
PROCEDURE Clear*;
VAR
i: LONGINT;
BEGIN {EXCLUSIVE}
AWAIT(readLock = 0);
FOR i := 0 TO count - 1 DO list[i] := NIL END;
count := 0
END Clear;
PROCEDURE GetItem* (i: LONGINT) : ANY;
BEGIN
ASSERT ((i >= 0) & (i < count), 101);
RETURN list[i]
END GetItem;
PROCEDURE Lock*;
BEGIN {EXCLUSIVE}
INC(readLock); ASSERT(readLock > 0)
END Lock;
PROCEDURE Unlock*;
BEGIN {EXCLUSIVE}
DEC(readLock); ASSERT(readLock >= 0)
END Unlock;
END List;
PROCEDURE Code (s: ARRAY OF CHAR): LONGINT;
VAR
i: INTEGER; a, b, c: LONGINT;
BEGIN
a := 0; b := 0; i := 0;
WHILE s[i] # 0X DO
c := b; b := a; a := (c MOD 509 + 1) * 127 + ORD(s[i]);
INC(i)
END;
IF b >= 32768 THEN b := b - 65536 END;
RETURN b * 65536 + a
END Code;
PROCEDURE ServerPacketInit* (command, seqnum: INTEGER; buf: Buffer);
BEGIN
buf.Clear;
buf.AddInt (VERSION, 2);
buf.AddInt (command, 2);
buf.AddInt (seqnum, 2);
END ServerPacketInit;
PROCEDURE ClientPacketInit* (command, seqnum: INTEGER; uin: LONGINT; buf: Buffer);
BEGIN
ServerPacketInit (command, seqnum, buf);
buf.AddInt (uin, 4);
END ClientPacketInit;
PROCEDURE BufGetSInt* (buf: String; VAR receiveBufOffset: LONGINT): INTEGER;
VAR
n: INTEGER;
BEGIN
n := ORD (buf^[receiveBufOffset]);
INC (receiveBufOffset);
RETURN n;
END BufGetSInt;
PROCEDURE BufGetInt* (buf: String; VAR receiveBufOffset: LONGINT): INTEGER;
VAR
b, n, i: INTEGER;
BEGIN
i := 0; b := 1; n := 0;
WHILE i < 2 DO
INC (n, ORD (buf^[receiveBufOffset + i]) * b);
b := b * 100H;
INC (i);
END;
INC (receiveBufOffset, 2);
RETURN n;
END BufGetInt;
PROCEDURE BufGetLInt* (buf: String; VAR receiveBufOffset: LONGINT): LONGINT;
VAR
i: INTEGER;
b, n: LONGINT;
BEGIN
i := 0; b := 1; n := 0;
WHILE i < 4 DO
INC (n, ORD (buf^[receiveBufOffset + i]) * b);
b := b * 100H;
INC (i);
END;
INC (receiveBufOffset, 4);
RETURN n;
END BufGetLInt;
PROCEDURE BufGetString* (buf: String; VAR receiveBufOffset: LONGINT): String;
VAR
len: LONGINT;
string: String;
BEGIN
len := BufGetInt (buf, receiveBufOffset);
NEW (string, len);
Strings.Copy (buf^, receiveBufOffset, len, string^);
INC (receiveBufOffset, len);
RETURN string;
END BufGetString;
PROCEDURE isNextSeqNum* (current, previous: INTEGER): BOOLEAN;
BEGIN
IF (previous < current) OR ((previous > current) & (previous > 0) & (current < 0)) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END;
END isNextSeqNum;
PROCEDURE SeqNumInACKList* (reqList: List; seqNum: INTEGER;
VAR req: ACKRec): BOOLEAN;
VAR
i: LONGINT;
ptr: ANY;
BEGIN
i := 0;
WHILE i < reqList.GetCount () DO
ptr := reqList.GetItem (i);
req := ptr (ACKRec);
IF seqNum = req.seqNum THEN
RETURN TRUE;
END;
INC (i);
END;
RETURN FALSE;
END SeqNumInACKList;
PROCEDURE CommandDecode* (command: INTEGER; VAR str: ARRAY OF CHAR);
BEGIN
CASE command OF
| ACK: str := "ACK";
| SEND_MESSAGE: str := "SEND_MESSAGE";
| LOGIN: str := "LOGIN";
| KEEP_ALIVE: str := "KEEP_ALIVE";
| SEND_TEXT_CODE: str := "SEND_TEXT_CODE";
| INFO_REQ: str := "INFO_REQ";
| NEW_USER_REG: str := "NEW_USER_REG";
| LOGIN_REPLY: str := "LOGIN_REPLY";
| USER_ONLINE: str := "USER_ONLINE";
| USER_OFFLINE: str := "USER_OFFLINE";
| RECEIVE_MESSAGE: str := "RECEIVE_MESSAGE";
| INFO_REPLY: str := "INFO_REPLY";
| NEW_USER_REPLY: str := "NEW_USER_REPLY";
ELSE
str := "Unknown";
END;
END CommandDecode;
(* BEGIN *)
(*Init;*)
END UDPChatBase.