home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_100 / 129_01 / hack.doc < prev    next >
Text File  |  1985-03-10  |  23KB  |  500 lines

  1. /************************************************************************/
  2. /*                hack.doc                */
  3. /************************************************************************/
  4.  
  5. /************************************************************************/
  6. /*                History                 */
  7. /*                                    */
  8. /* 82Dec07 CrT    Completed.                        */
  9. /* 82Nov23 CrT    Created.                        */
  10. /************************************************************************/
  11.  
  12. /************************************************************************/
  13. /*                Audience                */
  14. /*                                    */
  15. /* Folks trying to read or modify the Citadel code.            */
  16. /************************************************************************/
  17.  
  18. /************************************************************************/
  19. /*                Purpose                 */
  20. /*                                    */
  21. /* Explain the basic data structures and algorithms.            */
  22. /************************************************************************/
  23.  
  24. /************************************************************************/
  25. /*                Overview                */
  26. /************************************************************************/
  27.  
  28.     The fundamental structure of the system is very simple.  CtdlMsg.sys
  29. is a circular file.  New messages are simply written around the buffer
  30. in an endless circle, overwriting old messages in their way.  There is
  31. no other way of deleting messages, and text is never shuffled on disk.
  32. Messages are numbered consecutively and start with an FF (hex)
  33. byte.  Except for this FF start-of-message byte, all bytes in the message
  34. file have the high bit set to 0.  This means that in principle it is
  35. trivial to scan through the message file and locate message N if it
  36. exists, or return error.  (Complexities, as usual, crop up when we
  37. try for efficiency...)
  38.     Each room is basically just a list of message numbers.  Each time
  39. we enter a new message in a room, we slide all the old message-numbers
  40. down a slot, and probably the oldest one falls off the bottom.    Reading
  41. a room is just a matter looking up the messages one by one and printing
  42. them out.  If the message has been overwritten already, we just ignore it.
  43.     Implementing the "new message" function is also trivial in principle:
  44. we just keep track, for each caller in the userlog, of the highest-numbered
  45. message which existed on the >last< call.  (Remember, message numbers are
  46. simply assigned sequentially each time a message is created.  This
  47. sequence is global to the entire system, not local within a room.)  If
  48. we ignore all message-numbers in the room less than this, only new messages
  49. will be printed.  Voila!  Code up the system described thus far, and
  50. you'll have a good approximation to Version 1.    Better stop and reread
  51. everything to here, so you can pick out the fundamental mechanisms among
  52. all of Version 2's bells and whistles.
  53.  
  54. /************************************************************************/
  55. /*        message format on disk    (ctdlMsg.sys)            */
  56. /************************************************************************/
  57.  
  58. Message format has changed relative to V1, in the direction of using
  59. more disk space and providing greater flexibility.
  60.  
  61. A message now consists of a sequence of character strings.  Each string
  62. begins with a type byte indicating the meaning of the string and is
  63. ended with a null.  All strings are printable ASCII: in particular,
  64. all numbers are in ASCII rather than binary.  This is for simplicity,
  65. both in implementing the system and in implementing other code to
  66. work with the system.  For instance, a database driven off Citadel archives
  67. can do wildcard matching without worrying about unpacking binary data such
  68. as dates first.  To provide later downward compatability,
  69. all software should be written to IGNORE fields not currently defined.
  70.  
  71.  
  72. /************************************************************************/
  73. /*          The type bytes currently defined are:         */
  74. /************************************************************************/
  75.  
  76. BYTE    Mnemonic    Comments
  77.  
  78. 0xFF            Start-of-message indicator.  Followed by local
  79.             message ID# as ASCII string, null-terminated as
  80.             always.  This byte is the >only< byte which has
  81.             the high bit set in a Citadel message.buf file.
  82.             This field must be present in every message.
  83. A    Author        Name of originator of message.
  84. D    Date        Date message was created.
  85. M    Message     Text of message.  Is last field in a message, by
  86.             definition.  Following data will be ignored.
  87.             This field must be present in every message.
  88. N    Name        Human name for node originated on.  Used on
  89.             title line of foreign messages.  Ex:
  90.             ODD-DATA
  91.             will produce a title message something like
  92.             82Nov23 from Cynbe ru Taren @ODD-DATA
  93. O    Origin        ID of node message originated on: Country code plus
  94.             phone number of system.  (Not stored for locally
  95.             originated messages.)  Ex:
  96.             US 206 633 3282
  97. R    Room        Room of origin.  Topic.
  98. S    Source ID#    Message ID # on system message was created on.
  99.             Two 16-bit integers (high and low halves of
  100.             full 32-bit ID#) separated by a blank.    Ex:
  101.             0 13654
  102. T    To        Addressee.  Used only for private messages in Mail>,
  103.             in version 2.00 .
  104.             EXAMPLE
  105.  
  106. Let <FF> be a 0xFF byte, and <0> be a null (0x00) byte.  Then a message
  107. which prints as
  108.  
  109. LOGLAN> read new
  110.  
  111. 82Nov04 From James Cooke Brown
  112. Loi uiue i Ti logla
  113.  
  114. LOGLAN>
  115.  
  116. might be stored as
  117.  
  118. <FF>0 3583<0>D82Nov04<0>AJames Cooke Brown<0>RLOGLAN<0>MLoi uiue i Ti logla<0>
  119. |--Local ID--|---Date---|-----Author---------|--Room---|-------Message--------|
  120.  
  121. The date, room and author fields could be in any order. Not all fields
  122. are printed by default.  The local ID# and Room field are suppressed here.
  123. An isolated system will not normally have use for fields beyond those
  124. used in this example.
  125.  
  126. Lines are marked with C NewLine (ASCII LF) characters, within the message
  127. text proper.
  128.  
  129. /************************************************************************/
  130. /*                Networking                    */
  131. /************************************************************************/
  132.  
  133. Citadel nodes network by sharing one or more rooms.  Any Citadel node
  134. can choose to keep an image of any public room on any other Citadel node
  135. (subject to willingness to foot the phone bills, of course!).  The
  136. procedure in essence simply involves calling the imaged node up periodically
  137. and copying any new messages in the imaged room into the local image.
  138.  
  139. There is no necessary reciprocity or pre-arrangement, although convenience
  140. and politeness respectively suggest both.  The node which gets the
  141. information foots the phone bill for the transaction.  This promotes
  142. simple and harmonious relations between the nodes.
  143.  
  144. Complexities arise primarily from the possibility of densely connected
  145. networks: one does not wish to accumulate multiple copies of a given
  146. message, which can easily happen.  Nor does one want to see old messages
  147. percolating indefinitely through the system.
  148.  
  149. This problem is handled by a simple brute-force mechanism: each node
  150. keeps a list of all messages it has seen recently, recording origin
  151. system, creation date, and original ID#.  When downloading, messages
  152. which have already been seen, or which are too old to be remembered,
  153. are skipped.  Messages can percolate outward through a large network
  154. with no global routing or control, but do not reproduce wildly or
  155. cycle indefinitely.
  156.  
  157.  
  158. The above discussion should make the function of the new
  159. fields reasonably clear:
  160.  
  161.  o  Every message needs a local ID#, so the system can determine if
  162.     a given caller has seen it before.
  163.  o  Travelling messages need to carry system of origin, date of
  164.     origin, and original ID# with them, to keep reproduction and
  165.     cycling under control.
  166.  
  167.  
  168. (Uncoincidentally) the format used to transmit messages for networking
  169. purposes is precisely that used on disk, except that lines are marked
  170. with ASCII CR characters in stead of ASCII LF characters.  The current
  171. distribution does not include active network support code;  these comments
  172. are included in the hope of discouraging people from modifying the disk
  173. format, date field or whatever in ways which will make their local node
  174. incompatable with the network at large.
  175.  
  176. /************************************************************************/
  177. /*            portability problems                */
  178. /************************************************************************/
  179.  
  180. Modem code is covered elsewhere.
  181.  
  182. If porting to a C compiler other than BDS C, various problems are likely
  183. where low-level I/O takes place.  Look in particular at:
  184.  
  185. bios()        direct BIOS calls -- console access mostly.
  186. getc()
  187. getw()
  188. inp()        input from Z80 port.  Mostly modem-related.
  189. movmem()    block transfer in RAM
  190. outp()        output to Z80 port.   Mostly modem-related.
  191. putch()     console output
  192. rsrec()     set random-access mode for file
  193. rread()     random file read  by sectors
  194. rwrite()    random file write by sectors
  195. _spr()        mprintf(), dprintf() use this printf() support routine.
  196.  
  197. /************************************************************************/
  198. /*            "Room" records (ctdlRoom.sys)            */
  199. /************************************************************************/
  200. The rooms are basically indices into ctdlMsg.sys, the message file.
  201. As noted in the overview, each is essentially an array of pointers into
  202. the message file.  The pointers consist of a 16-bit message ID number
  203. (we will wrap around at 64K for these purposes) together with a 16-bit
  204. sector offset within ctdlMsg.sys telling us where the message begins.
  205.  
  206. Since messages are number sequentially and written circularly, the
  207. set of messages existing in ctdlMsg.sys will always form a continuous
  208. sequence at any given time.  Thus, by remembering the ID numbers of the
  209. oldest and newest messages in the message file, we can check to see
  210. if a message exists >before< going to disk, saving ourselves (and the
  211. disk drive) the pain of futile seeks in search of the nonexistent.
  212. This information is recorded in oldestHi/oldestLo (16-bit halves of
  213. 32-bit numbers, of which we use the low half here) and
  214. newestHi/newestLo.  You'll be seeing more of these...
  215.  
  216. The newestHi/newestLo is simply incremented each time we enter a
  217. new message in the message files.  OldestHi/oldestLo is incremented
  218. each time we overwrite an FF (start-of-message) byte in the course
  219. of writing a new message into the files.  This corresponds to dead
  220. reckoning -- current code never checks to see that the message number
  221. of the message we are overwriting is what we think it is.  In a garbaged
  222. file with extra FF bytes around, this could cause oldestHi/oldestLo to
  223. count too rapidly, eventually perhaps overtaking newestHi/newestLo,
  224. at which time the system will look completely empty.  If you suspect
  225. something like this is going on, just use configur.com to rebuild
  226. accurate numbers.
  227.  
  228. That should be enough background to tackle a full-scale room.  From
  229. 200ctdl.h :
  230.  
  231. struct {
  232.     char    rbgen;        /* generation number of room        */
  233.     char    rbflags;        /* same bits as flags above        */
  234.     char    rbname[NAMESIZE];    /* name of this room            */
  235.     char    rbdisk;        /* disk this room's files are in    */
  236.     char    rbuser;        /* user area for this room's files    */
  237.     union {
  238.     struct {
  239.         unsigned rbmsgNo;    /* every message gets unique#        */
  240.         int      rbmsgLoc;    /* sector message starts in        */
  241.     } msg[MSGSPERRM];
  242.     /* we'll ignore the other component here -- not used currently    */
  243.     } vp;
  244.     char rbOverRun[2];        /* actually unneeded just now        */
  245. } roomBuf;
  246.  
  247. [Note that all components start with "rb" for roomBuf, to make sure we
  248.  don't accidentally use an offset in the wrong structure. Be very careful
  249.  also to get a meaningful sequence of components --
  250.     roomBuf.vp.msg[i].rbmsgNo;
  251.  not
  252.     roomBuf.msg[i].rbmsgNo;
  253.  and certainly not
  254.     roomBuf.rbmsgNo;
  255.  BDS C provides no checking on this sort of stuff either.  (Fast
  256.  compilation on an 8080 has its prices!)]
  257.  
  258. Rbgen handles the problem of rooms which have died and been reborn
  259. under another name.  This will be clearer when we get to the log file.
  260. For now, just note that each room has a generation number which is
  261. bumped by one each time it is recycled.
  262.  
  263. Rbflags is just a bag of bits recording the status of the room.  The
  264. defined bits are:
  265. Bit 0:    INUSE.      1 if the room is valid, 0 if it is free for re-assignment.
  266. Bit 1:    PUBLIC.   1 if the room is visible by default, else 0.
  267. Bit 2:    CPMDIR.   1 if the room is a window onto some disk/userspace, else 0.
  268. Bit 3:    PERMROOM. 1 if the room should not be recycled even if empty, else 0.
  269.     (Lobby>, Mail> and all CPMDIRs are automatically permanent.)
  270.  
  271. Rbname is just an ASCII string (null-terminated, like all strings)
  272. giving the name of the room.
  273.  
  274. Rbdisk and rbuser are meaningful only in CPMDIR rooms, in which case
  275. they give the disk (0 == A:, 1==B: ... 15==P:) and userspace (0..31)
  276. to window.
  277.  
  278. Finally, msg is the array of pointers into the message file.  RbmsgNo
  279. is the ID number of the message, and RbmsgLoc is the sector it begins
  280. in.  (For NIL, we stick the value -1 in RbmsgLoc.)
  281.  
  282. RbOverRun[] at the tail end is a little kludge.  We read disk sectors
  283. directly into roomBuf, so it has to be two sectors long or something
  284. following will be overwritten.    If you resize msg[], be sure to
  285. recalculate the length of roomBuf and resize rbOverRun[] accordingly.
  286.  
  287.  
  288. RoomTab is just a little index into ctdlRoom.sys, to keep us from
  289. constantly pounding around on the disk looking for things.  It basically
  290. records the name and status of each room.  It is 100% redundant with
  291. the file itself... as all our indices are.  (As all indices should be!)
  292. Note that RoomTab is a significant consumer of RAM all by itself.  It
  293. is RAM well spent, but if you have to shave Citadel a few K to make
  294. it fit your system, cutting the number of rooms a bit is one try.
  295.  
  296. The only field new to us in roomTab is rtlastMessage, recording the
  297. most recent message in the room.  When we are searching for rooms with
  298. messages a given caller hasn't seen, we can check this number in RAM
  299. and avoid unprofitable disk accesses.
  300.  
  301. /************************************************************************/
  302. /*            log records (ctdlLog.sys)            */
  303. /************************************************************************/
  304. This is the fun one.  Get some fresh air and plug in your thinking cap
  305. first.    (Time, space and complexity are the eternal software rivals.
  306. We've got 256 log entries x about 500 messages spread over up to 128
  307. rooms to worry about, and with floppies disk access time is important...
  308. so perforce, we opt for lots of complexity to keep time and space in bounds.)
  309.  
  310. To understand what is happening in the log code takes a little persistence.
  311. You also have to disentangle the different activities going on and
  312. tackle them one by one.
  313.  
  314.  o    We want to remember some random things such as terminal screen
  315.     size, and automatically set them up for each caller at login.
  316.  
  317.  o    We want to be able to locate all new messages, and only new
  318.     messages, efficiently.    Messages should stay new even if it
  319.     takes a caller a couple of calls to get around to them.
  320.  
  321.  o    We want to remember which private rooms a given caller knows
  322.     about, and treat them as normal rooms.    This means mostly
  323.     automatically seeking out those with new messages.  (Obviously,
  324.     we >don't< want to do this for unknown private rooms!)    This
  325.     has to be secure against the periodic recycling of rooms
  326.     between calls.
  327.  
  328.  o    We want to support private mail to a caller.
  329.  
  330.  o    We want to provide some protection of this information (via
  331.     passwords at login) and some assurance that messages are from
  332.     who they purport to be from (within the system -- one shouldn't
  333.     be able to forge messages from established users).
  334.  
  335. Lifting another page from 200ctdl.h gives us:
  336.  
  337. struct logBuffer {
  338.     char    lbnulls;        /* # nulls to print after newline    */
  339.     char    lbflags;        /* UCMASK, LFMASK, EXPERT        */
  340.     char    lbwidth;        /* terminal width            */
  341.     char    lbname[NAMESIZE];   /* caller's name            */
  342.     char    lbpw[NAMESIZE];     /* caller's password        */
  343.     char    lbgen[MAXROOMS];    /* 5 bits gen, 3 bits lastVisit    */
  344.     char    lbvisit[MAXVISIT];  /* newestLo on last few calls    */
  345.     char    lbslot[MAILSLOTS];  /* for private mail         */
  346.     char    lbId[MAILSLOTS];    /* for private mail         */
  347.  
  348.     char    lbOverFlow[]        /*                    */
  349. }
  350.  
  351. Looks simple enough, doesn't it?  One topic at a time:
  352.  
  353.         RANDOM CONFIGURATION PARAMETERS
  354.  
  355. These are in the first three bytes in the record.  Lbnulls gives the
  356. number of nulls to print after a newline, for folks who like
  357. simultaneous hardcopy.    Or any remaining ASR33 aficionados calling up...
  358. Lbwidth is the caller's screen width.  We format all messages to this
  359. width, as best we can.    Lbflags is another bit-bag, recording
  360. uppercase-only folks, people who need a linefeed after a carraige-return,
  361. and people who want to suppress the little automatic hints all through
  362. the system.
  363.  
  364.  
  365.              FINDING NEW MESSAGES
  366.  
  367. This is the most important.  Thus, it winds up being the most
  368. elaborate.  Conceptually, what we would like to do is mark each
  369. message with a bit after our caller has read it, so we can avoid
  370. printing it out again next call.  Unfortunately, with 256 log
  371. entries this would require adding two sectors to each message... and
  372. we'd wind up reading off disk lots of messages which would never
  373. get printed.  So we resort to an arcane mixture of approximation
  374. and low animal cunning.
  375.  
  376. The approximation comes in doing things at the granularity of
  377. rooms rather than messages.  Messages in a given room are "new"
  378. until we visit it, and "old" after we leave the room... whether
  379. we read any of them or not.  This can actually be defended: anyone
  380. who passes through a room without reading the contents probably just
  381. isn't interested in the topic, and would just as soon not be dragged
  382. back every visit and forced to read them.  Given that messages are
  383. numbered sequentially, we can simply record the most recent message ID#
  384. of each room as of the last time we visited it.  With 128 rooms, this
  385. would give us (for each user) an array of 128 integers, or 256 bytes.
  386.  
  387. This is still too much -- I'd like the complete log record for a user
  388. to be 256 bytes or less, and we have other stuff to do yet.
  389.  
  390. So, we complicate a little more.  We record in lbvisit[MAXVISIT] the
  391. most recent message ID# in the system on each of the last six calls
  392. or so.    Now, for each room, we can just indicate which call we last
  393. visited the room on.  This takes 3 bits per room, which we stash in
  394. the low three bits of lbgen[MAXROOMS].    Now we're down to
  395. 128 rooms x 3 bits (plus a few bytes in lbvisit[], of course),
  396. which is quite reasonable.
  397.  
  398. Putting it all together, we can now compute whether a given room
  399. has new messages for our current caller without going to disk at all:
  400.  > We get the lbgen[] entry for the room in question
  401.  > We mask off the lower 3 bits
  402.  > We use the result as an index into lbvisit[], getting the ID number
  403.    of the most recent message in the system as of the last time we
  404.    visited the room.
  405.  > We compare this with roomTab[].rtlastMessage, which tells us
  406.    what the most recent message in the room is currently.
  407.  
  408.  
  409.          REMEMBERING WHICH PRIVATE ROOMS TO VISIT
  410.  
  411. This looks trivial at first glance -- just record one bit per room per
  412. caller in the log records.  The problem is that rooms get recycled
  413. periodically, and we'd rather not run through 256 log entries each
  414. time we do it.    So we adopt a kludge which should work 99% of the time.
  415.  
  416. As previously noted, each room has a generation number, which is bumped
  417. by one each time it is recycled.  As not noted, this generation number
  418. runs from 0 -> 31 (and then wraps around and starts over).  Thus, these
  419. numbers take 5 bits to represent.  By a miraculous coincidence, we have
  420. exactly 5 bits left in the lbgen[] entries in the log records.    [Anyone
  421. familiar with "capability" pointers will be encountering deja vu here...]
  422.  
  423. When someone visits a room, we set the generation number in lbgen[]
  424. equal to that of the room.  This flags the room as being available.
  425. If the room gets recycled, on our next visit the two generation numbers
  426. will no longer match, and the room will no longer be available -- just
  427. the result we're looking for.  (Naturally, if a room is marked as PUBLIC,
  428. all this stuff is irrelevant.)
  429.  
  430. This leaves only the problem of an accidental matchup between the two
  431. numbers giving someone access to a Forbidden Room.  We can't eliminate
  432. this danger completely, but it can be reduced to insignificance for
  433. most purposes.    (Just don't bet megabucks on the security of this system!)
  434. Each time someone logs in, we set all "wrong" generation numbers to be
  435. one less than the actual generation of the room. This means that the
  436. room must be recycled thirty-one times before an accidental matchup
  437. can be achieved.  (We do this for all rooms, INUSE or dead, public
  438. or private, since any of them may be reincarnated as a Forbidden Room.)
  439.  
  440. Thus, for someone to accidentally be lead to a Forbidden Room, they
  441. must establish an account on the system, then not call until some room
  442. has been recycle thirty-one to thirty-two times, which room must be
  443. reincarnated as a Forbidden Room, which someone must now call back
  444. (having not scrolled off the userlog in the mean time) and read new
  445. messages.  The last clause is about the only probable one in the sequence.
  446. The danger of this is much less than the danger that someone will
  447. simply guess the name of the room outright...
  448.  
  449.  
  450.              SUPPORTING PRIVATE MAIL
  451.  
  452. Can one have an elegant kludge?  This must come pretty close.
  453.  
  454. Private mail is sent and recieved in the Mail> room, which otherwise
  455. behaves pretty much as any other room.    To make this work, we store
  456. the actual message pointers in lbslot[] and lbId[] in the caller's
  457. log record, and then copy them into the Mail> room array whenever we
  458. enter the room.  This requires a little fiddling to get things just
  459. right.    We have to update roomTab[MAILROOM].rtlastMessage at login
  460. to reflect the presence or absence of new messages, for example.  And
  461. MakeMessage() has to be kludged to ask for the name of the recipient
  462. of the message whenever a message is entered in Mail>.    But basically
  463. it works pretty well, keeping the code and user interface simple and
  464. regular.
  465.  
  466.  
  467.            PASSWORDS AND NAME VALIDATION
  468.  
  469. LogTab[] indexes ctdlLog.sys, giving us a quick way of finding people.
  470. It is basically a chronologically sorted hash table.  We keep a two-byte
  471. hash of the name and password of each caller in RAM.  When someone tries
  472. to log in, we just whip through the table in order looking for matches
  473. on the password hash and loading the matching logfile entry in.  Bogus
  474. hits are eliminated by the simple expedient of refusing to acknowledge
  475. a new user who's name or password hashes on top of an existing user.
  476. Computer chauvinism at it's best...
  477. This makes it difficult to forge messages from an existing user.  (Fine
  478. point: nonprinting characters are converted to printing characters, and
  479. leading, trailing, and double blanks are deleted.)
  480. 
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487.  
  488.  
  489.  
  490.  
  491.  
  492.  
  493.  
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500.