Sage.UDPChatBase.Mod 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. MODULE UDPChatBase; (** AUTHOR "SAGE"; PURPOSE "UDP Chat base" *)
  2. IMPORT
  3. Kernel, Strings, BIT, IO := Streams, IP, FS := Files;
  4. CONST
  5. serverPort* = 14000;
  6. UserFile = "Sage.UDPChatUsers.dat"; (* user file *)
  7. clientKeepAliveInterval* = 20000;
  8. clientKeepAliveAwait* = clientKeepAliveInterval * 3 + clientKeepAliveInterval DIV 2;
  9. UDPHdrLen = 8;
  10. MaxUDPDataLen* = 10000H - UDPHdrLen;
  11. VERSION* = 0002H; (* Identifies the packet as an ICQ packet *)
  12. ACK* = 000AH; (* Acknowledgement *)
  13. SEND_MESSAGE* = 010EH; (* Send message through server (to offline user) *)
  14. LOGIN* = 03E8H; (* Login on server *)
  15. CONTACT_LIST* = 0406H; (* Inform the server of my contact list *)
  16. SEARCH_UIN* = 041AH; (* Search for user using his/her UIN *)
  17. SEARCH_USER* = 0424H; (* Search for user using his/her name or e-mail *)
  18. KEEP_ALIVE* = 042EH; (* Sent to indicate connection is still up *)
  19. SEND_TEXT_CODE* = 0438H; (* Send special message to server as text *)
  20. LOGIN_1* = 044CH; (* Sent during login *)
  21. INFO_REQ* = 0460H; (* Request basic information about a user *)
  22. EXT_INFO_REQ* = 046AH; (* Request extended information about a user *)
  23. CHANGE_PASSWORD* = 049CH; (* Change the user's password *)
  24. STATUS_CHANGE* = 04D8H; (* User has changed online status (Away etc) *)
  25. LOGIN_2* = 0528H; (* Sent during login *)
  26. UPDATE_INFO* = 050AH; (* Update my basic information *)
  27. UPDATE_EXT_INFO* = 04B0H; (* Update my extended information *)
  28. ADD_TO_LIST* = 053CH; (* Add user to my contact list *)
  29. REQ_ADD_TO_LIST* = 0456H; (* Request authorization to add to contact list *)
  30. QUERY_SERVERS* = 04BAH; (* Query the server about address to other servers *)
  31. QUERY_ADDONS* = 04C4H; (* Query the server about globally defined add-ons *)
  32. NEW_USER_1* = 04ECH; (* Ask for permission to add a new user *)
  33. NEW_USER_REG* = 03FCH; (* Register a new user *)
  34. NEW_USER_INFO* = 04A6H; (* Send basic information about a new user *)
  35. CMD_X1* = 0442H; (* *Unknown *)
  36. MSG_TO_NEW_USER* = 0456H; (* Send a message to a user not on my contact list
  37. (this one is also used to request permission to add someone with 'authorize'
  38. status to your contact list)
  39. *)
  40. LOGIN_REPLY* = 005AH; (* Login reply *)
  41. USER_ONLINE* = 006EH; (* User on contact list is online/has changed online status *)
  42. USER_OFFLINE* = 0078H; (* User on contact list has gone offline *)
  43. USER_FOUND* = 008CH; (* User record found matching search criteria *)
  44. RECEIVE_MESSAGE* = 00DCH; (* Message sent while offline/through server *)
  45. END_OF_SEARCH* = 00A0H; (* No more USER_FOUND will be sent *)
  46. INFO_REPLY* = 0118H; (* Return basic information about a user *)
  47. EXT_INFO_REPLY* = 0122H; (* Return extended information about a user *)
  48. STATUS_UPDATE* = 01A4H; (* User on contact list has changed online status (Away etc) *)
  49. REPLY_X1* = 021CH; (* *Unknown (returned during login) *)
  50. REPLY_X2* = 00E6H; (* *Unknown (confirm my UIN?) *)
  51. UPDATE_REPLY* = 01E0H; (* Confirmation of basic information update *)
  52. UPDATE_EXT_REPLY* = 00C8H; (* Confirmation of extended information update *)
  53. NEW_USER_UIN* = 0046H; (* Confirmation of creation of new user and newly assigned UIN *)
  54. NEW_USER_REPLY* = 00B4H; (* Confirmation of new user basic information *)
  55. QUERY_REPLY* = 0082H; (* Response to QUERY_SEVERS or QUERY_ADDONS *)
  56. SYSTEM_MESSAGE* = 01C2H; (* System message with URL'ed button *)
  57. MESSAGE_TYPE_NORMAL* = 0001H; (*the message is a normal message*)
  58. MESSAGE_TYPE_URL* = 0004H; (*the message is an URL, and actually consists of two parts,
  59. separated by the code FE.
  60. The first part is the description of the URL, and the second part is the
  61. actual URL.*)
  62. MESSAGE_TYPE_DATA* = 0008H;
  63. TYPE
  64. String = Strings.String;
  65. ACKRec* = POINTER TO RECORD
  66. seqNum*: INTEGER;
  67. END;
  68. Client* = OBJECT
  69. VAR
  70. ip*: IP.Adr;
  71. port*: LONGINT;
  72. inSeqNum*, outSeqNum*: INTEGER;
  73. uin*: LONGINT;
  74. keepAliveTimer*: Kernel.MilliTimer;
  75. ACKList-: List;
  76. PROCEDURE &New*;
  77. BEGIN
  78. NEW (ACKList);
  79. END New;
  80. PROCEDURE Finalize*;
  81. BEGIN
  82. ACKList.Clear;
  83. END Finalize;
  84. END Client;
  85. UserInfo* = POINTER TO RECORD
  86. uin*: LONGINT;
  87. shortName*, fullName*, eMail*: ARRAY 65 OF CHAR;
  88. END;
  89. User* = POINTER TO RECORD (UserInfo)
  90. password*: LONGINT;
  91. END;
  92. Users* = OBJECT
  93. VAR
  94. list: List;
  95. lastUIN: LONGINT;
  96. PROCEDURE &New*;
  97. BEGIN
  98. (* Reading of passwords *)
  99. NEW (list);
  100. lastUIN := 1000;
  101. Load;
  102. END New;
  103. PROCEDURE Load;
  104. VAR
  105. u: User;
  106. f: FS.File;
  107. r: FS.Reader;
  108. BEGIN
  109. f := FS.Old (UserFile);
  110. IF f # NIL THEN
  111. FS.OpenReader (r, f, 0);
  112. WHILE r.res = IO.Ok DO
  113. NEW (u);
  114. r.RawLInt (u.uin);
  115. r.RawLInt (u.password);
  116. r.RawString (u.shortName);
  117. r.RawString (u.fullName);
  118. r.RawString (u.eMail);
  119. IF r.res = IO.Ok THEN
  120. IF u.uin > lastUIN THEN
  121. lastUIN := u.uin
  122. END;
  123. list.Add (u);
  124. END;
  125. END;
  126. END;
  127. END Load;
  128. PROCEDURE Store*;
  129. VAR
  130. f: FS.File; w: FS.Writer;
  131. i: LONGINT;
  132. u: User;
  133. ptr: ANY;
  134. BEGIN
  135. IF list.GetCount () > 0 THEN
  136. f := FS.New (UserFile);
  137. IF (f # NIL) THEN
  138. FS.OpenWriter(w, f, 0);
  139. i := 0;
  140. WHILE (w.res = IO.Ok) & (i < list.GetCount ()) DO
  141. ptr := list.GetItem (i);
  142. u := ptr (User);
  143. w.RawLInt(u.uin);
  144. w.RawLInt(u.password);
  145. w.RawString(u.shortName);
  146. w.RawString(u.fullName);
  147. w.RawString(u.eMail);
  148. INC (i);
  149. END;
  150. IF w.res = IO.Ok THEN
  151. w.Update;
  152. FS.Register (f)
  153. END
  154. END
  155. END
  156. END Store;
  157. PROCEDURE Add* (password, shortName, fullName, eMail: String): User;
  158. VAR
  159. u: User;
  160. BEGIN
  161. NEW (u);
  162. INC (lastUIN);
  163. u.uin := lastUIN;
  164. u.password := Code (password^);
  165. COPY (shortName^, u.shortName);
  166. COPY (fullName^, u.fullName);
  167. COPY (eMail^, u.eMail);
  168. list.Add (u);
  169. RETURN u;
  170. END Add;
  171. PROCEDURE Find* (uin: LONGINT): User;
  172. VAR
  173. i: LONGINT;
  174. u: User;
  175. ptr: ANY;
  176. BEGIN
  177. i := 0;
  178. WHILE i < list.GetCount () DO
  179. ptr := list.GetItem (i);
  180. u := ptr (User);
  181. IF uin = u.uin THEN
  182. RETURN u;
  183. END;
  184. INC (i);
  185. END;
  186. RETURN NIL;
  187. END Find;
  188. PROCEDURE PasswordCorrect* (uin: LONGINT; password: String): BOOLEAN;
  189. VAR
  190. u: User;
  191. BEGIN
  192. u := Find (uin);
  193. IF u # NIL THEN
  194. IF Code (password^) = u.password THEN
  195. RETURN TRUE;
  196. END;
  197. END;
  198. RETURN FALSE;
  199. END PasswordCorrect;
  200. END Users;
  201. Buffer* = OBJECT (Strings.Buffer)
  202. PROCEDURE AddInt* (n, len: LONGINT);
  203. VAR
  204. i: INTEGER;
  205. b: LONGINT; res: WORD;
  206. s: ARRAY 4 OF CHAR;
  207. BEGIN
  208. ASSERT (len <= 4);
  209. i := 0; b := 1;
  210. WHILE i < len DO
  211. s[i] := CHR (BIT.LAND ((n DIV b), 0FFH));
  212. b := b * 100H;
  213. INC (i);
  214. END;
  215. Add (s, 0, len, TRUE, res)
  216. END AddInt;
  217. END Buffer;
  218. PArray = POINTER TO ARRAY OF ANY;
  219. (** Lockable Object List. *)
  220. List* = OBJECT
  221. VAR
  222. list: PArray;
  223. count: LONGINT;
  224. readLock: LONGINT;
  225. PROCEDURE &New*;
  226. BEGIN
  227. NEW (list, 8); readLock := 0
  228. END New;
  229. (** return the number of objects in the list. If count is used for indexing elements (e.g. FOR - Loop)
  230. in a multi-process situation, the process calling the GetCount method should call Lock before
  231. GetCount and Unlock after the last use of an index based on GetCount *)
  232. PROCEDURE GetCount*() : LONGINT;
  233. BEGIN
  234. RETURN count
  235. END GetCount;
  236. PROCEDURE Grow;
  237. VAR
  238. old: PArray;
  239. i: LONGINT;
  240. BEGIN
  241. old := list;
  242. NEW (list, LEN(list) * 2);
  243. FOR i := 0 TO count - 1 DO list[i] := old[i] END;
  244. END Grow;
  245. (** Add an object to the list. Add may block if number of calls to Lock is bigger than the number of calls
  246. to Unlock *)
  247. PROCEDURE Add*(x : ANY);
  248. BEGIN {EXCLUSIVE}
  249. AWAIT (readLock = 0);
  250. IF count = LEN (list) THEN Grow END;
  251. list[count] := x;
  252. INC (count)
  253. END Add;
  254. (** return the index of an object. In a multi-process situation, the process calling the IndexOf method
  255. should call Lock before IndexOf and Unlock after the last use of an index based on IndexOf.
  256. If the object is not found, -1 is returned *)
  257. PROCEDURE IndexOf * (x : ANY) : LONGINT;
  258. VAR
  259. i: LONGINT;
  260. BEGIN
  261. i := 0 ; WHILE i < count DO IF list[i] = x THEN RETURN i END; INC(i) END;
  262. RETURN -1
  263. END IndexOf;
  264. (** Remove an object from the list. Remove may block if number of calls to Lock is bigger than
  265. the number of calls to Unlock *)
  266. PROCEDURE Remove* (x : ANY);
  267. VAR
  268. i: LONGINT;
  269. BEGIN {EXCLUSIVE}
  270. AWAIT (readLock = 0);
  271. i:=0; WHILE (i < count) & (list[i] # x) DO INC(i) END;
  272. IF i < count THEN
  273. WHILE (i < count - 1) DO list[i] := list[i + 1]; INC(i) END;
  274. DEC(count);
  275. list[count] := NIL
  276. END
  277. END Remove;
  278. (** Removes all objects from the list. Clear may block if number of calls to Lock is bigger than
  279. the number of calls to Unlock *)
  280. PROCEDURE Clear*;
  281. VAR
  282. i: LONGINT;
  283. BEGIN {EXCLUSIVE}
  284. AWAIT(readLock = 0);
  285. FOR i := 0 TO count - 1 DO list[i] := NIL END;
  286. count := 0
  287. END Clear;
  288. (** return an object based on an index. In a multi-process situation, GetItem is only safe in a locked
  289. region Lock / Unlock *)
  290. PROCEDURE GetItem* (i: LONGINT) : ANY;
  291. BEGIN
  292. ASSERT ((i >= 0) & (i < count), 101);
  293. RETURN list[i]
  294. END GetItem;
  295. (** Lock previousents modifications to the list. All calls to Lock must be followed by a call to Unlock.
  296. Lock can be nested*)
  297. PROCEDURE Lock*;
  298. BEGIN {EXCLUSIVE}
  299. INC(readLock); ASSERT(readLock > 0)
  300. END Lock;
  301. (** Unlock removes one modification lock. All calls to Unlock must be preceeded by a call to Lock. *)
  302. PROCEDURE Unlock*;
  303. BEGIN {EXCLUSIVE}
  304. DEC(readLock); ASSERT(readLock >= 0)
  305. END Unlock;
  306. END List;
  307. (*IntervalTimer* = OBJECT (WMComponents.Component)
  308. VAR
  309. running, terminated: BOOLEAN;
  310. interval: LONGINT;
  311. t: Kernel.Timer;
  312. onTimer- : WMEvents.EventSource;
  313. PROCEDURE &Init;
  314. BEGIN
  315. Init^;
  316. NEW (t);
  317. interval := 500;
  318. (* event *)
  319. NEW (onTimer, SELF, GSonTimer, GSonTimerInfo, SELF.StringToCompCommand);
  320. events.Add (onTimer);
  321. BEGIN {EXCLUSIVE}
  322. running := TRUE
  323. END;
  324. END Init;
  325. PROCEDURE SetInterval* (i: LONGINT);
  326. BEGIN
  327. BEGIN {EXCLUSIVE}
  328. interval := i
  329. END;
  330. END SetInterval;
  331. PROCEDURE Finalize*;
  332. BEGIN
  333. Finalize^;
  334. running := FALSE;
  335. t.Wakeup;
  336. BEGIN {EXCLUSIVE}
  337. AWAIT (terminated)
  338. END;
  339. END Finalize;
  340. BEGIN {ACTIVE}
  341. BEGIN {EXCLUSIVE}
  342. AWAIT (running)
  343. END;
  344. terminated := FALSE;
  345. WHILE running DO
  346. onTimer.Call (NIL);
  347. t.Sleep (interval);
  348. END;
  349. BEGIN {EXCLUSIVE}
  350. terminated := TRUE
  351. END;
  352. END IntervalTimer;*)
  353. (*VAR
  354. GSonTimer, GSonTimerInfo: String;*)
  355. (*PROCEDURE Init;
  356. BEGIN
  357. GSonTimer := Strings.NewString("onTimer");
  358. GSonTimerInfo := Strings.NewString("Is called when timer ticks");
  359. END Init;*)
  360. PROCEDURE Code (s: ARRAY OF CHAR): LONGINT;
  361. VAR
  362. i: INTEGER; a, b, c: LONGINT;
  363. BEGIN
  364. a := 0; b := 0; i := 0;
  365. WHILE s[i] # 0X DO
  366. c := b; b := a; a := (c MOD 509 + 1) * 127 + ORD(s[i]);
  367. INC(i)
  368. END;
  369. IF b >= 32768 THEN b := b - 65536 END;
  370. RETURN b * 65536 + a
  371. END Code;
  372. PROCEDURE ServerPacketInit* (command, seqnum: INTEGER; buf: Buffer);
  373. BEGIN
  374. buf.Clear;
  375. buf.AddInt (VERSION, 2);
  376. buf.AddInt (command, 2);
  377. buf.AddInt (seqnum, 2);
  378. END ServerPacketInit;
  379. PROCEDURE ClientPacketInit* (command, seqnum: INTEGER; uin: LONGINT; buf: Buffer);
  380. BEGIN
  381. ServerPacketInit (command, seqnum, buf);
  382. buf.AddInt (uin, 4);
  383. END ClientPacketInit;
  384. PROCEDURE BufGetSInt* (buf: String; VAR receiveBufOffset: LONGINT): INTEGER;
  385. VAR
  386. n: INTEGER;
  387. BEGIN
  388. n := ORD (buf^[receiveBufOffset]);
  389. INC (receiveBufOffset);
  390. RETURN n;
  391. END BufGetSInt;
  392. PROCEDURE BufGetInt* (buf: String; VAR receiveBufOffset: LONGINT): INTEGER;
  393. VAR
  394. b, n, i: INTEGER;
  395. BEGIN
  396. i := 0; b := 1; n := 0;
  397. WHILE i < 2 DO
  398. INC (n, ORD (buf^[receiveBufOffset + i]) * b);
  399. b := b * 100H;
  400. INC (i);
  401. END;
  402. INC (receiveBufOffset, 2);
  403. RETURN n;
  404. END BufGetInt;
  405. PROCEDURE BufGetLInt* (buf: String; VAR receiveBufOffset: LONGINT): LONGINT;
  406. VAR
  407. i: INTEGER;
  408. b, n: LONGINT;
  409. BEGIN
  410. i := 0; b := 1; n := 0;
  411. WHILE i < 4 DO
  412. INC (n, ORD (buf^[receiveBufOffset + i]) * b);
  413. b := b * 100H;
  414. INC (i);
  415. END;
  416. INC (receiveBufOffset, 4);
  417. RETURN n;
  418. END BufGetLInt;
  419. PROCEDURE BufGetString* (buf: String; VAR receiveBufOffset: LONGINT): String;
  420. VAR
  421. len: LONGINT;
  422. string: String;
  423. BEGIN
  424. len := BufGetInt (buf, receiveBufOffset);
  425. NEW (string, len);
  426. Strings.Copy (buf^, receiveBufOffset, len, string^);
  427. INC (receiveBufOffset, len);
  428. RETURN string;
  429. END BufGetString;
  430. PROCEDURE isNextSeqNum* (current, previous: INTEGER): BOOLEAN;
  431. BEGIN
  432. IF (previous < current) OR ((previous > current) & (previous > 0) & (current < 0)) THEN
  433. RETURN TRUE;
  434. ELSE
  435. RETURN FALSE;
  436. END;
  437. END isNextSeqNum;
  438. PROCEDURE SeqNumInACKList* (reqList: List; seqNum: INTEGER;
  439. VAR req: ACKRec): BOOLEAN;
  440. VAR
  441. i: LONGINT;
  442. ptr: ANY;
  443. BEGIN
  444. i := 0;
  445. WHILE i < reqList.GetCount () DO
  446. ptr := reqList.GetItem (i);
  447. req := ptr (ACKRec);
  448. IF seqNum = req.seqNum THEN
  449. RETURN TRUE;
  450. END;
  451. INC (i);
  452. END;
  453. RETURN FALSE;
  454. END SeqNumInACKList;
  455. PROCEDURE CommandDecode* (command: INTEGER; VAR str: ARRAY OF CHAR);
  456. BEGIN
  457. CASE command OF
  458. | ACK: str := "ACK";
  459. | SEND_MESSAGE: str := "SEND_MESSAGE";
  460. | LOGIN: str := "LOGIN";
  461. | KEEP_ALIVE: str := "KEEP_ALIVE";
  462. | SEND_TEXT_CODE: str := "SEND_TEXT_CODE";
  463. | INFO_REQ: str := "INFO_REQ";
  464. | NEW_USER_REG: str := "NEW_USER_REG";
  465. | LOGIN_REPLY: str := "LOGIN_REPLY";
  466. | USER_ONLINE: str := "USER_ONLINE";
  467. | USER_OFFLINE: str := "USER_OFFLINE";
  468. | RECEIVE_MESSAGE: str := "RECEIVE_MESSAGE";
  469. | INFO_REPLY: str := "INFO_REPLY";
  470. | NEW_USER_REPLY: str := "NEW_USER_REPLY";
  471. ELSE
  472. str := "Unknown";
  473. END;
  474. END CommandDecode;
  475. (* BEGIN *)
  476. (*Init;*)
  477. END UDPChatBase.