BACKGROUND

The basic function of a bulletin board is to allow groups of users to
share messages among each other.  The basic functions are storage and
retrieval of messages, and this action is generally referred to as a
"conversation."

Each message has a unique identifier that is created when the message
is stored, and is specified when retrieving a message.  A method of
determining what identifiers are valid is required to retrieve a
message: thus, another operation is to get a list of what message
identifiers exist.  To be useful, the board should keep track of which
messages have already been retrieved by a particular user, allowing
that user to select among the "new" or "old" messages.

On a board of this size, it is not enough to manually select messages
out of such a list; there must be some organization of these messages
to make them useful and usable.  The method we've currently selected
is to group the messages into units called "rooms".  The list
operation is therefore split into two parts: listing the available
rooms, and listing the messages stored within a room.

Even with this grouping of messages into rooms, the board can still be
intimidating.  Users need some way to decide which conversations are
useful to them and which are not.  Therefore, the board allows each
user to assign a priority to each room, to list only those rooms above
a certain priority, and to specify what priority newly created rooms
should take.

Messages within a room are intended to be related through some common
criteria: by a topic, such as "unix hints", or, in the case of
"private" or "restricted" conversations such as the "internal" room on
the Cyber board, by the users allowed to participate in the
conversation.  The first criterion is enforced retroactively by a
room's "moderators", which may remove inappropriate messages (or
perhaps move them to an appropriate room); the second is enforced by
the program, under control of the moderators.  This requires an
additional set of operations: the ability to delete or move a message,
and the ability to specify which users may access a room.

This management of the user base can become unwieldy.  To simplify the
matter somewhat, I'm adopting the concept of user groups, similar to
the group file on unix machines.  Yet another set of operations is
required to create and manipulate these groups.

Statistics from prototype board:
	Active Messages		6285
	Active Rooms		 162
	Users			 538

IMPLEMENTATION

	The functions of the bulletin board are split into two parts,
in a client/server model.  The server manipulates the databases at the
direction of the client program.  There may be many user interfaces
accessing the same board (for example, a NeXT interface).

	Since the implementation of the various user interfaces varies
extremely, this description concentrates on the common elements: the
data structures, and server operations.  These, together, describe the
server protocol as it is designed: the implementation of the protocol
is handled by Sun's rpcgen compiler.

Basic Data Structures:

	The message, room, account, and room_user structures form the
basic elements of their respective databases.  The data structures are
specified in the Remote Procedural Call Language, which is similar to
the C Programming Language.  The most obvious extension is "<>", which
means an array of varying size.  Another extension is that "struct
struct-name" automatically does a typedef of "struct-name".

	The message structure contains administrative information,
some information for the users, and the message itself:

	struct message {
	  message_id id;	/* a unique index */
	  account_id poster;	/* the account which posted the message */
	  time_t expiration;	/* expiration time of the message */
	  int line_count;	/* number of liens in the message */
	  time_t date_posted;	/* date when the message was posted */
	  string nickname<>;	/* the poster's nickname, for the from: line */
	  string comment<>;	/* supplied by the user, for the summary */
	  string message<>;	/* the text of the message itself */
	};

	The room structure contains administrative information about
the room, and some information for the benefit of the users:

	struct room {
	  room_id id;			/* a unique index */
	  string name<>;		/* the "name" of the room */
	  account_id owner;		/* the account which owns the room */
	  time_t default_expiration;	/* default message expiration time */
	  permission default_permission;/* default permission of the room */
	  time_t last_change;		/* time of last change */
	  int active_messages;		/* number of messages in the room */
	  time_t creation_date;		/* creation date of the room */
	  string comment<>;		/* ...describing the room's contents */
	};

	The account structure, as every other, has an administrative
side and a informative side:

	struct account {
	  account_id id;		/* a unique index */
	  string name<>;		/* the bbs "id" */
	  string email_address<>;	/* where to send mail for this user */
	  string nickname<>;		/* the user's nickname */
	  time_t last_access_date;	/* when and where the account ... */
	  string last_access_source<>;	/* ... was last accessed */
	};

	The room_user structure stores the permission

	struct room_user {
	  room_id room;			/* the room and user form a ... */
	  account_id user;		/* ... unique index */
	  permission permission;	/* permission level for the room */
	  priority priority;		/* priority assigned by the
					   user to this room */
	  opaque messages_read<>;	/* stored for the client program:
					   not interpreted by the server */
	};

	The permissions (access levels) for users within a room are:

	enum permission {
	  no_access,			/* user cannot access this room */
	  read_only,			/* user can read, but not post */
	  read_write,			/* user can read and post */
	  moderator			/* user is a moderator of this room */
	};

Server Operations:

	What follows is a list of the supported operations for each
database.  In parenthesis are commands related to these operations.
The remove procedure definitions for each operation are listed, though
not in the Remote Procedural Call Language (RPCL), which is slightly
unwieldy for this application (since you must only pass one parameter
and return one result, of predefined types).  As an example of how to
translate this format into RPCL:

	{x,y,z} blegga(a xa,b xb,c xc);

becomes, in RPCL,

	struct blegga_result switch (boolean status) {
	    case true:
		x *x;
		y *y;
		z *z;
	    case false:
		string error<>;
	}

	struct blegga_pars {
		a xa;
		b xb;
		c xc;
	}

	...

	blegga_result blegga(blegga_pars) = XXX; /* XXX is a unique number */

The most important thing to note is that every routine returns a
status, and in the case of failure, an error message--so, though neither
are shown in this format, they should be implied.

		{room} create_room(string room_name);
		{} remove_room(room_id room_id);
		{room} change_room(room new_info);
		{room_id<>} list_rooms(void);
		{room} retrieve_room(room_id room_id);

		{account} create_account(string id,string passwd);
		{} remove_account(account_id account_id);
		{account} change_account(account new_info);
		{account_id<>} list_accounts();
		{account} retrieve_account(account_id account_id);

		{message} create_message(
			room_id room,
			string subject,
			string message_text);
		{} remove_message(message_id);
		{message} change_message(message message);
		{message_id<>} list_messages(room_id room_id);
		{message} get_message(message_id message_id);

		{} store_room_user(room_user);
		{room_user} retrieve_room_user(room_id room,account_id user);

Version 1.2 (currently running):

	The above was my design thoughts on version 2.0, which would
use RPC.  Version 1.2 is based on plain TCP/IP sockets.  It may be
that it's not worth it to completely overhaul a working system
(version 1.2)--instead, we may wish to extend the current one
slightly, and improve the user interface.

	For 1.2, I wrote a library of client routines which
communicate with the server program, so that the writers of user
interfaces would be shielded from the mechanics of sockets, and so I
could change the client/server protocol without having to change every
user interface.


-------------------------------------------------------------------------------
extern char client_error[512];
	In case of error (indicated by a return value of 0 from any
	routine), includes text explaining the cause.

typedef struct {
  char **list;
  int count;
} result_list;
	Used any time we return a list of items.  "list" is often cast
	as a list of pointers to structures, rather than pointers to
	characters.

int server_open(char *server_hostname);
	Opens a connection to the server located on machine "server_hostname".

int client_connected();
	Tests whether the client still has a connection open to the server.

int client_signon(char *id, char *passwd, char *default_room, boolean create);
	Sign onto the server: "id" is the user's bbs id, "passwd" is
	their bbs passwd, "default_room" is a returned value containing
	their default (initial) room, and "create" specifies whether
	this is a new account that should be created.

int client_test();
	A "ping" test to/from the server.

int client_changeclass(char *id, class_t class);
	Changes another user's access class within the current room.

int client_changeroom(char *room, char *owner, time_t *default_expiration,
		      class_t *default_class, char *summary_line);
	Change the characteristics of a room.

int client_create(char *room, char *summary);
	Create a new room.

int client_default_room(char *room);
	Set this user's default (initial) room.

int client_delete(int messagenumber);
	Delete a message in the current room.

result_list *client_directory();
	Retrieve a list of rooms, with room topic, count of new
	messages (for this user), hidden status, and this user's
	access class.

int client_hide(char *room,int hide);
	Hide a room (similar to setting a room's priority, in 2.0).

int client_kill_server();
	Shutdown the server.  (Only works from certain bbs accounts.)

int client_list_users(result_list *result);
	List users of the current room, with their class: useful for
	finding out which users are moderators, etc.

int client_nick(char *nick);
	Set this user's nickname.

int client_password(char *password);
	Set this user's password.

int client_post(char *room, char *buf, int buf_l);
	Post the message (of length buf_l) contained in buf,
	in room "room".

int client_read(int messagenumber, char **buf, int *buf_l);
	Retrieve a posting from the server.

int client_remove(char *room);
	Remove a room.

directory_info *client_roominfo(char *room);
	Retrieve information on a specific room.  directory_info is
	similar to the room type in 2.0, only it also contains
	room_user information (such as whether the room is hidden).

int client_select(char *input_room, int *messagenumber);
	Select a new room as the current room.
	
int client_signoff();
	Sign off from bbs.

result_list *client_summary();
	Get a summary of the messages in the current room.

result_list *client_users();
	Get a list of users currently signed on.

user_info *client_whois(char *id);
	Find information about another user (for example, from which
	account he most recently signed on.

-------------------------------------------------------------------------------

Server Internals (version 1.2):

	Messages within bbs are stored as plain text files, grouped by
room into subdirectories of the MESSAGEBASE directory.  For example,
posting #503 in the general room is found in the file "general/0503".
Header information for a message is also stored in an ndbm(3) database
called "room", for fast retrieval.

	Room_User information is also stored in the "room" database.
Currently, messages_read is just an integer specifying the last
message read in <room> by <user>, and is updated by the get_message
(client_read) operation.  (Actually, to avoid updating the database
every time a user reads a message, Room_User information is cached in
memory, and only flushed when a client selects a new room or signs
off.)

	Room information is stored in another ndbm database, called
"directory".  It's stored in a separate database to speed traversals
of the entire room list, which are common.  In addition to the
information in struct room, defined above, it contains First, Final,
and Next message indices.  First gives the number of the first active
(undeleted, non-expired) message in a room, Final gives the number of
the final active message, and Next gives the number of the next
message to be posted.  (Next != Final because Final is decremented if
the current Final message is deleted.  Next guarantees a uniquely new
message number.)

	Account information is stored in a third ndbm database, called
"user", because traversals of this list are also common.

	Let's proceed to an overview of what happens in a sample bbs
session.

	First, the client connects to the server, with server_open().
The server accepts the connection, and initializes some data
structures related to the client, containing such information as
whether the client has signed on, where it's connecting from, and when
the client last sent a message (for timeout purposes).  The only
operations allowed at this point are client_test (the ping operation)
and client_signon, which tells the server which user this is.  Our
client proceeds to signon, which allows the server to fill in the rest
of its client data structure by looking up the client in the User
database.  At this point, the server sends back the user's default
room.

	Once the client signs on, a typical operation will be to find
out which rooms have new messages.  It does this by calling
client_directory().  When the server receives a client_directory
command, it traverses the room database, feeding the information it
finds to the client.  For each room, it also sends additional
information from the Room_User database, specifying which rooms are
hidden and which have unread messages.  Each room is preceeded by a
SERVER_ACK token, and when the server finishes traversing all the
rooms, it sends a SERVER_EOF.  The client_directory() routine compiles
this information into a list, and returns an array and room count.

	Our client proceeds to select a room.  The server retrieves
Room_User information (caching it), checks permissions, and if the
client is allowed to access this room, sends back which messages have
been read (or, currently, the last message read).

	Our client now asks for a summary of the messages within the
room, with client_summary().  The server looks up the first and last
message numbers in the room database, and attempts to retrieve summary
information for every message in that range from the "room" database.
As it does this, it checks the expiration date of those messages, and
expires those past due.  Any deleted messages are skipped; the server
sends information on the rest back to the client, similar to the way
in which it sends the results from a client_directory().

	The client now picks messages from this summary list one at a
time, which the server retrieves from the files in MESSAGEBASE, and
sends back.  The server updates Messages_Read (in cache) as necessary.

	The client eventually selects another room or signs off, at
which point the server flushes its Room_User cache for that client.
If the client has signed off, it closes its end of the connection.

