This is docs/design.info, produced by makeinfo version 4.6 from docs/design.texinfo. This manual is for MOZ (MOO in Oz) version 1.0. Copyright (C) 2002 Robin Lee Powell Permission is granted to distribute and modify as long as credit is given. See the file license.txt in the main MOZ distribution for full copyright information.  File: design.info, Node: Top, Next: Requirements, Up: (dir) This is the design documentation for MOZ (Moo in OZ). MOO is Mud Object Oriented. MUD is Multi-User Dungeon or Dimension. In general, a MUD is a multi-user text-based virtual environment. For information on MUDs in general, see The MUD Resource Collection (http://www.godlike.com/muds/) or your local search engine. For information on MOOs, see The MOO-Cows FAQ (http://www.moo.mud.org/moo-faq/). Oz is a multi-paradigmatic language that happens not to suck. See The Main Mozart-Oz Page (http://www.mozart-oz.org/). Note that the requirements document is also incorporated herein, and that this document is changing in response to actual code decisions, and hence is not strictly a design document. Oh well. * Menu: * Requirements:: An informal requirements specification. Normally this would be a seperate document, but I wanted to be able to refer back to the requirements as appropriate. * Security Overview:: MOZ uses capability security, which flies in the face of what most people think they know about security issues, especially people used to MOOs, even though the concepts are quite intuitive. A basic description is given here, with URLs to longer explications. This section is first because otherwise the reader is likely to get confused later. * One-Instance Classes:: There are a number of classes that are instantiated exactly once per MOZ server. They are listed here, along with brief notes as to the sort of information they store. * Ex-DB Design:: MOZ doesn't have much in the way of ex-db code, unless you count the Oz language itself. What it does have is detailed here. * Class Definitions:: Description of the core classes. * Storage Issues:: The Storage class is a one-instance class, and is complicated and important enough to deserve its own section. * MOZ Records:: The Oz record type is used in the MOZ code fairly frequently. In some cases the format of those records is quite important, and needs to be understood to write new classes. Those records are discussed in detail here. * Security In Detail:: Security in MOZ is capability-based, and hence rather different than every other MUD I'm aware of. Please try to forget everything you know about security before reading this section. * Unsorted:: Point-form notes that haven't been fully fleshed out.  File: design.info, Node: Requirements, Next: Security Overview, Prev: Top, Up: Top Requirements ************ An informal requirements set for Moo in OZ. Note that these are in no particular order. They are anchored for reference in those parts of the design document that relate to them. 1. multiple muds can be joined together transparently 2. ability to make as many changes on the fly as possible 3. maximum simplicity for building and coding 4. not too slow or too memory-hoggy 5. number of connections limited only by physical issues (bandwidth, memory, etc) 6. robust access control, within the MOZ, of objects, of internal code, of objects characteristics, of processes, etc. 7. ability to handle various (spoken) languages 8. high potentiol for user enjoyment 9. fault tolerance 10. ease of debugging 11. user can see general information, including class code, about objects in the MOZ 12. ease of testing 13. ease of modification on the part of new admin, programmers, and builders  File: design.info, Node: Security Overview, Next: One-Instance Classes, Prev: Requirements, Up: Top Security Overview ***************** Capability security is a security model that is most likely entirely different than what you're used to. It is simple and elegant and fast. The main drawbacks are that it has the potential to make it much easier for users to do incredibly stupid things, but that really depends on how the system is set up, and that it is very hard to bootstrap. The latter is solved by persistence, see *Note Persistence::. The former is solved in MOZ by simply not allowing the transfer of capabilities that are innapropriate. OK, time to step back a bit and explain more about capability security as a concept. The type of security that you are most probably used to I'm going to call ACL (Access Control List) security. The way this basically works is that if you want to, say, open a file, somewhere buried in the operating system is a test that compares your user name to a list of people allowed to open the file. Or if you're in a MUD and want to pick up an object, same thing: somewhere an if statement tests if you should be allowed to do that. The basic idea with ACLs is that you can always find what it is that you want to access. File trees have ways to get listings of all the file names, which you can then pass to an open command of some kind. Rooms have a list of all the objects in them, which you can then pass to a get command of some kind. Capability security is completely different. Capability security is based on the idea of a key to a vault: you either have the key, or you don't. If you don't have the key, you can't open the vault (at least, not without a whole lot of time and effort; good cryptography, which MOZ as of this writing only sort of uses, can make the time involved into the trillions of years). Better still, in most cases in a capability system you can't even _see_ the vault (again, without a lot of time and effort and access to the data in some kind of raw form, such as access to the disk itself). If you can't point to the vault or see it or touch it, it's ridiculous to talk about opening it: you don't have the _capability_ to do so at all (hence the name of this type of security). Basically, a capability is a reference to some kind of operation on an object (i.e. generally it's a reference to a procedure). If you don't have the capability, and you can't forge the capability (because you can't see the object it's acting or you can't guess the capability's name/secret/key/whatever), then you can't access that aspect of the object. Note that the key analogy holds in other ways: you can copy capabilities, and you can give them to other objects. This is what leads to the first drawback mentioned above: players can quite easily "give away the homeworld" Babylon 5 Quotes (http://www.keri.paintedhippo.com/quotepage.html#b5). Some effort is made to make this difficult (note that requiring actual Oz programming to transfer a capability is considered 'difficult'), but it's part of the nature of the system that this cannot be stopped. On the other hand, capabilities can be revoked. In fact, being able to tell an object to revoke a capability is, itself, a capability in MOZ! Which leads to one of the other nice things about capabilities: they're arbitrarily extendable. You want to make another one, you just program it in and pass it on. Quite trivial, whereas, in general, ACL-based systems require reworking of at least some of the system code to add a new ACL. Furthermore, you can pass around a procedure that has a capability inside it that it will only use once. Since procedures can't be opened up or de-compiled in Oz, this is quite safe. An important aspect of capabilities is that, unlike ACL systems, an object can, by default, do _nothing_. At least, nothing that involves other objects. This is very important: hacks of ACL systems often revolve around tricking the ACL into not activating, or believing a user is someone they're not, or something. This simply isn't possible in a capability system: there's nothing to trick. To even _ask_ for a capability you don't have requires a capability! This motivates the next section, which defines an initial set of capabilities. For more information, see The Three Parts of Security (http://www.erights.org/elib/capability/3parts.html), Introduction To Capability Based Security (http://www.skyhunter.com/marcs/capabilityIntro/index.html), and An Ode to the Granovetter Diagram (http://www.erights.org/elib/capability/ode/index.html). Note that almost all security in MOZ, is done from an in-language, in-db perspective. Some aspects of the security rely on ex-DB code (the ActiveObject wrapper in particular), but these are the exception rather than the rule. If it's important enough to you to break into a MOZ that you start eavesdropping transmissions between MOZs to figure out how Oz encodes its name information, you're welcome to hack the thing as far as I'm concerned. Go get laid or something, you're a loser.  File: design.info, Node: One-Instance Classes, Next: Ex-DB Design, Prev: Security Overview, Up: Top One-Instance Classes ******************** There are a number of classes which are instantiated exactly once per server, i.e. there is exactly one object of each of these classes on each server. These objects are used to store and manage server-wide persistent values. For example, the Storage object keeps track of all the files that are used to store objects to disk. Because of this, I will often refer to the class and the object interchangeably; the Storage class and the Storage object are largely the same thing in practice, as there is only ever one Storage object made from the Storage class. The one-instance classes are listed below, along with a brief description of the types of data they store. `The Storage Object' The Storage object stores information about the disk files used to store objects and classes. It stores associations between Oz names and file numbers and object wrappers and other data. *Note Storage Issues:: `The Server Object' The Server object stores a list of players and their associated passwords. `The LanguageStrings Object' The LanguageStrings objects stores localized strings for everything that the core system classes need to output to the user. This is so that people wishing to localize MOZ to a new language don't need to go looking for strings all over the place. `The Help Object' The Help object stores all the basic help information of the MOZ.  File: design.info, Node: Ex-DB Design, Next: Class Definitions, Prev: One-Instance Classes, Up: Top Ex-DB Design ************ The phrase "Ex-DB" refers to the idea that in MUD code, anything you can change from within the MUD, without having to reset it or something, is "In-DB", as in "in the database". By extension, anything that you cannot change from within the MUD is "Ex-DB", or "out of the database". MOZ is very different from other MUDs. The server, as such, is nearly non-existent; it's just a tiny bootstrap module (*note R2::,*note R7::), plus a few accessory modules. This is because all things in Oz, including classes and objects, are first-class values. Even the the class of an object can be changed on the fly. * Menu: * The moz Program:: The operations performed by the actual moz binary are detailed here. * The new_moz Program:: The operations performed by the new_moz binary are detailed here. * The Parsing Module:: Describes an accessory module used for arbitrary string parsing. * The EnhancedString Module:: Describes an accessory module used for simple string processing (but less simple than the built-in String module). * The Active Object Wrapper:: Objects in MOZ are 'active'. That is, there is a single thread for each object that deals with all aspects of that object. This section discusses details of the design of the active object wrapper that implements that behaviour. It is very unlikely that any modifications to this code will be necessary. Any modifications to this code are likely to break _everything_. * The MozCompilation Module:: Describes an accessory module used for easier access to the Oz compilation process It is very unlikely that any modifications to this code will be necessary. Any modifications to this code are likely to break _everything_. * Object References:: Dealing with the active object wrapper directly is difficult, due to having to keep track of a seperate list of capabilities, so we wrap it in an Oz object.  File: design.info, Node: The moz Program, Next: The new_moz Program, Up: Ex-DB Design The moz Program =============== The main program binary, moz, performs the following functions: 1. Compiles MozBase.class using MozCompilation 2. Compiles Storage.class using MozCompilation 3. Creates a new, empty Storage object by hand 4. Uses Storage to load itself into itself 5. Starts the Storage object 6. Asks the Storage object where to find the Server object (i.e. what the Server object's file number is). 7. Tells storage to load the server object 8. Starts the Server object 9. Holds until the server is shut down 10. Ask Storage to write everything to disk 11. Writes the Storage object to disk by hand.  File: design.info, Node: The new_moz Program, Next: The Parsing Module, Prev: The moz Program, Up: Ex-DB Design The new_moz Program =================== The new_moz program creates a bare, clean, and very boring MOZ instance. It's just like the moz program except that it creates inital instantiations of all of the one-instance classes, and it doesn't load the Storage object from disk.  File: design.info, Node: The Parsing Module, Next: The EnhancedString Module, Prev: The new_moz Program, Up: Ex-DB Design The Parsing Module ================== The Parsing module is a set of monadic parser combinators, which can be used to parse any CFG in a pretty simple, straight-forward way. The may be more expressive than CFGs with a bit of work, I don't know. The code is basically a re-write of the code at Monadic Parser Combinators (http://www.cs.nott.ac.uk/~gmh/bib.html#monparsing), which is a lovely tutorial on monadic parsing. This code will eventually be on MOGUL, when I manage to get around to it.  File: design.info, Node: The EnhancedString Module, Next: The Active Object Wrapper, Prev: The Parsing Module, Up: Ex-DB Design The EnhancedString Module ========================= This is just a re-packaging of Duchier's MOGUL String Package (http://www.mozart-oz.org/mogul/info/duchier/string.html), because it's nice to have the better string handling capabilities around.  File: design.info, Node: The Active Object Wrapper, Next: The MozCompilation Module, Prev: The EnhancedString Module, Up: Ex-DB Design The Active Object Wrapper ========================= The active object wrapper that MOZ uses is one of the more complicated pieces of code in the MOZ, as well as the piece that may seem oddest to users of other object oriented langauges. Also, it is the largest piece of ex-db code. The active object wrapper is actually a procedure that is passed an object. It returns an object record with full capabilities. An object record consists of a wrapper procedure, the objects Oz name, and a capability set. All the wrapper procedure does is pass its one argument to a port (an Oz port, _not_ a TCP/IP port). It's what's at the other end of that port that's interesting. A thread is created to process the stuff passed in the port. Note that this means that each object has exactly one thread associated with it, and this thread is the only one that ever gets to affect the object directly. This make things much easier as far as multi-threading issues go, as no locking needs to be done within object methods. This wrapper also gives insultion: no-one ever sees the object directly except this thread, and this is what allows the capability security system to work. Also, the thread can do other processing besides just calling the method it has been passed. The most important part of this processing is capability processing. Basically, what's passed to the port is not the method name, it's a record with an Oz name for a label. This label is used to key a dictionary, and if a method name associated with it is found, that method is called, if not, an error is returned. For more information, see *Note Capability Implementation::. Another, much cooler, thing that the wrapper thread does is handle upgrade requests. upgrade is a virtual method, in that it is called just like any other method in the wrapper, using an Oz name capability, but it doesn't exist in the object, rather it exists in the wrapper. An upgrade call takes a class name and actually swaps out the object it's wrapping for one of that class! It calls `toRecord' and `fromRecord' to make sure information is preserved, and lets the Storage object know the new class name. Then it just goes back to processing the port like nothing happened, and from the point of view of every other object in the mud, nothing did! There are a couple of other miscellaneous things the wrapper does. In particular, it inserts capability information into a `toRecord' call and extracts it from a `fromRecord' call. When a new object is created, the capability dictionary is built using the class's methodList feature, with the addition of the special capability 'upgrade', and a bunch of new Oz names. In many cases, these are then overwritten by a `fromRecord' call. The object itself will often need to know about its wrapper and capabilities, so that it can inform other objects. This is handled by a method on every object in the MOZ called activeObjectInform, which takes those two pieces of information as arguments, generally to store on attributes. This method is called every time wrapper information is changed. The Storage object is also informed of this information at the same time. *Note General Class Issues::.  File: design.info, Node: The MozCompilation Module, Next: Object References, Prev: The Active Object Wrapper, Up: Ex-DB Design The MozCompilation Module ========================= The MozCompilation module exports a single procedure, `Compile', which takes the file name to compile and the environment to compile it under. If the file contains only an expression, such as a single functor, that is returned. The environment is given as a list of 2-tuples. The first part of each 2-tuple is an atom representing the variable name under which that aspect of the environment will be visible in the compilation. The second part is the actual value being made so visible. Note that if one of the variables passed in to the compilation is unbound, binding it in the compilation will effectively work as output. If you didn't understand that, that's probably fine; this isn't a user-servicable part of the code, and I don't really even understand it myself.  File: design.info, Node: Object References, Prev: The MozCompilation Module, Up: Ex-DB Design Object References ================= Dealing with the active object wrapper directly is difficult, due to having to keep track of a seperate list of capabilities, so we wrap it in an Oz object. This wrapping is done, most of the time anyways, by using the procedure MAKEOBJECTREFERENCE, supplied by ActiveObject.oz. For arguments, it takes an ActiveObject wrapper, a record of capabilities, and the Oz name of the object (more for convenience than anything else), and returns an object which runs the wrapper but also implements the methods "wrapper", "capabilities", and "ozName", which simply return the information passed to them in both the first argument and the argument with the same label as the method name.  File: design.info, Node: Class Definitions, Next: Storage Issues, Prev: Ex-DB Design, Up: Top Class Definitions ***************** * Menu: * General Class Issues:: Caveats for class creation, and a list of methods that every class should (re-) define. * The Class Hierarchy:: I apologize for the lack of a pretty tree. Deal. * class MozBase:: The bottom-level class. * class Storage:: The persistent storage class. * class Server:: The incoming TCP/IP handling class. * class Described:: The basic described object. Has no location. Normally not instantiated. * class Located:: Basic located object (i.e. has an intrinsic location). * class Mobile:: Basic mobile object (i.e. has an intrinsic location that can be changed). * class Location:: Basic location object (i.e. has contents). * class Player:: Takes parsed input and calls commands. * class Wizard:: Just like Player, but with more capabilities, and hence some extra commands. * class Puppet:: An automaton version of Player. * class Container:: Combination of the features of Mobile and Location; a thing that can have things in it. * class Exit:: A connector between Locations. Has no location itself. * class Gate:: Like Exit, but connects locations between two MOZs. * class Terminus:: Like Location, but provides a connecting point for a Gate. * class Connection:: Handles TCP/IP connections. * class Parser:: Parses user input and deals with finding the right verbs to pass the user's input to. * class Control:: Used to generate objects used to control other objects. * class ClassControl:: Used to generate objects used to create and control new classes.  File: design.info, Node: General Class Issues, Next: The Class Hierarchy, Up: Class Definitions General Class Issues ==================== The `start' Method ------------------ Every class has a `start' method, which is run at the end of the process of the object being loaded in by Storage. Note that this is run _any_ time the object is loaded, including as a result of upgrade calls. Therefore, `start' must not assume, well, _anything_. For example, the `start' method on a wandering puppet, that starts its wandering from room to room, must not assume anything about what room it starts in. Note that any initialization of anything that is not completely permanent should happen using the `start' method, not the `init' method. The `init' Method ----------------- Every class has an `init' method, which is run by Storage as part of the object creation and wrapping process. The `init' method, as a minimum, takes an object record for Storage and LanguageStrings and takes the object's own Oz name. _NOTE_: The `init' method should only be called once for the _entire_ lifetime of the object. Anything that is not absolutely permanent should not be passed to `init'. Also, an `init' record can be passed to createObject on Storage, but be aware that Storage will change the name of the record to `init' and tack on three arguments at the beginning for the Oz name and the Storage and LanguageStrings object references. The `toRecord' Method --------------------- Every class has a `toRecord' method, which returns a record containing all the aspects of the object that must be persistent (*note Loading And Unloading::. Note that reference to objects _must_ be converted to their appropriate Oz names before being made part of the record, because the actual object code cannot be stored on disk. The names in question are only used for persistence purposes, and knowing them should give no privilege(1). The `fromRecord' Method ----------------------- Every class has a `fromRecord' method, which takes the output of `toRecord' and applies the information therein to the object (*note Loading And Unloading::). Oz names representing objects are turned back into object references as part of this process. `toRecord', `fromRecord', and `exports' --------------------------------------- The `toRecord' method uses a feature called `exports', which is a list of 2-tuples of the format Name#Type, where both Name and Type are atoms. The former is the attribute name to be saved, and the latter is the type of the attribute. Note that these types are specific to MOZ, and not Oz itself. The Type information is used to determine when special processing is needed to be able to safely save an attribute to disk. For example, `toRecord' uses the Type field to look for object references, which need special processing. It is very important that the `export' list contain only the names of attributes that contain non-stateful data, or else the save will fail. Stateful data is data that is specific to a particular invocation of a running program, such as open file handles. Oz programmers also refer to stateful data as 'resources'. Simple records, atoms, Oz names, and procedures are all safe. Threads, file handles, and any other value that isn't guaranteed to be persistent, are all unsafe to save. Any attribute that stores this form of data either must not be named in the `export' list, or there must be special code in `toRecord' to deal with that Type, such as there is for the type called `objectRef', which is for references to objects. There is also an analogous `featExports' list for saving the value of features. Since features are write-once values, an object that has a featExports list should only have fromRecord called on it once. Any subsequent calls once the features have been initialized will fail. Recognized `exports' Types in `toRecord' and `fromRecord' --------------------------------------------------------- Type values that currently mean somethng: `dict', `objectRefDict', `notPersistent', and `objectRef'. Anything else will be treated as in no way special. This means if it has non-persistent data, an error will result. The standard `toRecord'/`fromRecord' methods have a Type `objectRef' that has a fairly specialized format. These attributes are used to refer to other objects in the MOZ. It expects the attribute to which it refers to be a record with the field names `ozName', `wrapper' and `capabilities' which point to the Oz name of the object, the active object wrapper procedure and the capabilities that this object has on the object being referenced. The list of capabilities is a list of methods on the object pointed to by the `objectRef' that the holder of the `objectRef' can use. Note that this means that an object cannot save persistently information about an object whose Oz name it cannot retrieve, but this should not be a limitation in general. The Type `notPersistent' is used to pass non-persistent data, such as the output port for a Player object to send data to the player, through upgrade calls. It is not used for `toRecord' calls for data destined to be writen to disk, as an error will result when trying to store that type of data (as the concept makes very little sense). Examples ........ Note that these examples are _not_ valid MOZ code, or even Oz code really. They are just intended to give a bit of a visual idea of how `toRecord', `fromRecord', `exports' and `objectRef'-Type attributes interact. * A simple object: DrinkingGlass173 To store DrinkingGlass173 to disk when it is not being used, we use `toRecord'. In turn, `toRecord' uses `exports' and the `objectRef' Type. This is what part of the object it might look like: `exports' list: name#string description#string storageRef#objectRef Note that `name' is not a specially recognized attribute in Oz or MOZ; it is user-defined, and objects need not have it. In MOZ, most objects do in fact have a `name', but it is not required. An `objectRef' is an Oz object itself, wrapping the ActiveObject wrapper for easier use. The information it contains consists of the wrapper for the object it refers to, the object's ozName, and some capabilities on that object, which we will represent like so: storageRef: wrapper ozName capabilities: capabilities( createObject objectRefFromRecord getClass upgradeObject ) Note that every object that gets save to disk has at least one entry in its `exports' list that is of Type `objectRef': `storageRef', which is used to allow the object to create other objects and load class code and such. * A complex object: "Robin's Room" To store "Robin's Room" we use the same `toRecord' methed. This is what part of the object it might look like: `exports' list: name#string description#string storageRef#objectRef contents#objectRefDict . . . Contents: contents( blueChair redChair drinkingGlass173 ) . . . BlueChair: objectRef( wrapper ozName capabilities: capabilities( getName getDescription ) ) `methodList' ------------ Every class has a `methodList' feature that is a list of all the method names that are used outside the class. This is used by the active object wrapper, and is safe since nothing but the active object wrapper ever talks directly to the object anyways. Note that the `methodList' feature _should not_ include upgrade, as this is added in by the active object wrapper. All objects are dynamically upgradeable a la A dynamically-extensible server (http://www.mozart-oz.org/documentation/dstutorial/node3.html#label199)(2) ---------- Footnotes ---------- (1) It is possible that the current method of doing `toRecord', in that it is defined on MozBase, will turn out to be a security hole, because objects can lie about themselves. If so, it should be moved onto ActiveObject. (2) Note that this also makes them stationary, which may cause problems later (although I don't think so) and means that what's actually being passed around are procedures, not objects. For more information, see *Note The Active Object Wrapper::.  File: design.info, Node: The Class Hierarchy, Next: class MozBase, Prev: General Class Issues, Up: Class Definitions The Class Hierarchy =================== This is a basic outline of which classes descend from which other classes. * *Note class MozBase:: * *Note class Storage:: * *Note class Server:: * *Note class Described:: * *Note class Location:: * *Note class Container::, also a child of *Note class Mobile:: * *Note class Player::, also a child of *Note class Mobile:: * *Note class Terminus:: * *Note class Located:: * *Note class Mobile:: * *Note class Container::, also a child of *Note class Location:: * *Note class Player::, also a child of *Note class Location:: * *Note class Wizard:: * *Note class Puppet:: * *Note class Control:: * *Note class ClassControl:: * *Note class Exit:: * *Note class Gate:: * *Note class Connection:: * *Note class Parser::  File: design.info, Node: class MozBase, Next: class Storage, Prev: The Class Hierarchy, Up: Class Definitions class MozBase ============= An extension of BaseObject, and an ancestor of _every_ other class. MozBase has an 'ozname' feature, and the `init' call initializes 'ozname' to {NewName}, as well as storing a reference to the Storage class. MozBase also has a method 'ozname' that returns this name. All of the methods listed in *Note General Class Issues:: are first defined in MozBase. MozBase will not usually be directly instantiated.  File: design.info, Node: class Storage, Next: class Server, Prev: class MozBase, Up: Class Definitions class Storage ============= class Storage deals with the saving and loading of classes and objects, including doing Active Object wrapping (*note The Active Object Wrapper::). It also provides support for mapping between file names, Oz names, and object references (*note Associations::). In fact, the Storage class does an awful lot of things. It passes initial capabilities to the object and remembers which class each object belongs to (*note Initial Capabilities::). It stores capabilitites on each object for purposes of Wizardry (*note Wizardry::). It runs the `start', `init', `toRecord' and `fromRecord' methods on objects and is usually the only object to do so.  File: design.info, Node: class Server, Next: class Described, Prev: class Storage, Up: Class Definitions class Server ============ The Server object just (of which there is only one in each MOZ) sits and watches the MOZ's TCP/Ip port. When it recieves a connection, it instantiates a new object of class Connection and calls the `start' method on that object. It also instantiates an object of class Parser. It then connects both the Connection and Parser objects to the Player object so that the player's commands can be processed.  File: design.info, Node: class Described, Next: class Located, Prev: class Server, Up: Class Definitions class Described =============== The Described class is not usually instantiated. It adds name- and description-related capabilities to MozBase. It has no location or contents attributes.  File: design.info, Node: class Located, Next: class Mobile, Prev: class Described, Up: Class Definitions class Located ============= The Located class expands upon the Described class to add location information, and read-only operations upon that information.  File: design.info, Node: class Mobile, Next: class Location, Prev: class Located, Up: Class Definitions class Mobile ============ The Mobile class expands upon the Located class to add write operations on location information.  File: design.info, Node: class Location, Next: class Player, Prev: class Mobile, Up: Class Definitions class Location ============== The Location class expands upon the Located class to add contents information, and operations upon that information.  File: design.info, Node: class Player, Next: class Wizard, Prev: class Location, Up: Class Definitions class Player ============ The player object holds all the attributes, methods and verbs associated with being a player of the MOZ. The Parser object associated with each player is given full capabilities on the Player object in question.  File: design.info, Node: class Wizard, Next: class Puppet, Prev: class Player, Up: Class Definitions class Wizard ============ A Wizard is just a user with more extensive capabilities. The Wizard class provides commands to use those capabilities more effectively, although being of the Wizard class does not give one Wizardly powers, nor is the converse true.  File: design.info, Node: class Puppet, Next: class Container, Prev: class Wizard, Up: Class Definitions class Puppet ============ The Puppet class expands the Player class into a form suitable for use as an automaton, and includes some basic ability to respond to its master's commands.  File: design.info, Node: class Container, Next: class Exit, Prev: class Puppet, Up: Class Definitions class Container =============== The Container class is a merging of the Mobile and Location classes, i.e. a Mobile with contents.  File: design.info, Node: class Exit, Next: class Gate, Prev: class Container, Up: Class Definitions class Exit ========== The Exit class connects two locations. Each Exit is a one-way pointer to the `destination'. An Exit object in a room will respond to verbs such as 'go' and its own name, and put players through to the destination location. Since it is both an important operation and one that exemplifies a lot of issues, here's what happens when the player wants to move from one location to another: 1. The player's command is parsed and, eventually, a 'go' method (or whatever) is called on the exit. 2. The exit asks the current location to give the player to the destination location. The process goes something like this: 1. Updates the player object's location information with a given argument. 2. Gives capabilities on the player to the remote location. 3. Removes the player from itself. 3. The exit decides if it wants to allow the operation. This may include examining the player's inventory for a key object of some kind. 4. Note, however, that the exit itself does not have the capabilities on the player required to do the movement; those belong to the location itself.  File: design.info, Node: class Gate, Next: class Terminus, Prev: class Exit, Up: Class Definitions class Gate ========== The Gate class expands on the Exit class with the ability to use an Oz ticket to connect to a room in another MOZ. It gets this ticket from a Terminus class object. A Gate must be blessed by a Wizard to be able to connect using tickets.  File: design.info, Node: class Terminus, Next: class Connection, Prev: class Gate, Up: Class Definitions class Terminus ============== A Terminus class object is just a Location with the added ability to generate an Oz ticket for itself, to be used by a Gate. A Terminus must be blessed by a Wizard to be able to produce tickets.  File: design.info, Node: class Connection, Next: class Parser, Prev: class Terminus, Up: Class Definitions class Connection ================ Objects of class Connection have no Active Object wrapping, as they are created on the fly be the server. They accept input solely from a TCP/IP port which they parse _very lightly_ and pass to the parser object. By 'parse' I mean, basically, break into lines. They also accept streams from the Server object, which the Server treats as output strings and passes back out the TCP/IP port.  File: design.info, Node: class Parser, Next: class Control, Prev: class Connection, Up: Class Definitions class Parser ============ Objects of class Parser have no Active Object wrapping, as they are created on the fly by the server. There is one Parser per player connection. It accepts input from the Connection object, parses it, and goes looking for verbs that match it, which are then called with the parsed arguments. There are, however, situations in which entering new commands should not be possible. In particular, situations in which user input is required to complete a command, such as a yes/no dialog. For these occurences, a procedure on the Parser object is passed down the call chain. This procedure can be used to suck additional input. It uses a simple capability which is revoked as soon as the procedure returns back to the parser.  File: design.info, Node: class Control, Next: class ClassControl, Prev: class Parser, Up: Class Definitions class Control ============= This class is used to create an ohat can be used to control another object. Every time a player creates an object, another object of class Control is also created Storage, and is given the ability to retrieve perfect capabilities for the object the player asked for. This control object is then placed in the player's inventory, while the object the player asked for is left unlocated. A second copy of the control rod is placed in Limbo, for Wizardly use. Please note that Control is used to perform mutations, and as such cannot itself be mutated. This is quite deliberate: mutating a Control class object could very easily give access to specialized capabilities.  File: design.info, Node: class ClassControl, Prev: class Control, Up: Class Definitions class ClassControl ================== This class is used to write to and recompile classes created by users. It is created when the player runs a "create class" command. Like the Control class, objects of the ClassControl class cannot, and should not, be mutated.  File: design.info, Node: Storage Issues, Next: MOZ Records, Prev: Class Definitions, Up: Top Storage Issues ************** * Menu: * Associations:: How files, object Oz names, and objects are associated. * Loading And Unloading:: MOZ is disk based; that is, when started it loads only the minimum number of objects necessary and loads and saves things on the fly as the server runs. This section is a basic description of how that works.  File: design.info, Node: Loading And Unloading, Prev: Associations, Up: Storage Issues Loading And Unloading ===================== The Storage object for the MOZ is the first object to be run. It loads all the classes it can find when its `start' method is run and sets up dictionaries and such so other objects can find each other. MOZ is disk based, that is, there is no loading of objects at the beginning of the MOZ, besides some of the one-instance objects. This turns out to be quite trivial in Oz: when objects are loaded, they are pointed to by a weak dictionary. When they are no longer pointed to by anything else, the weak dictionary facility is used to save them to disk. After the main program loads the Storage object, it loads the Server object and runs that. Everything else is loaded from there as needed. When an object is loaded, any of its attributes that are of type `objectRef' (as defined in the `exports' list) are instantiated not with the appropriate object wrapper but with a procedure defined by the Storage object that checks if the object is currently actually loaded from disk. If it is, the procedure returns the wrapper. If not, the procedure loads the object from disk and then returns the wrapper if not. A full-shutdown save process goes as follows (individual object saves can be inferred): for each entry in the dictionary that maps objects to file names, call the object's `toRecord' and save the result of that to the appropriately named file. Objects are stored in files named .obj, where is a number that is only used internally. These files are saved in direcories named after the class of the object.  File: design.info, Node: Associations, Next: Loading And Unloading, Up: Storage Issues Associations ============ Objects and classes are stored in files, and must be stably accessible between invocations of moz. Therefore objects and classes must have static names which are used to refer to them by other objects within the MOZ, and there must be associations between those names and the objects themselves (well, the active object wrappers really, see *Note The Active Object Wrapper::), as well as between the file names the objects are stored in and their MOz names (or the objects themselves). All these mappings should be two-way, but mappings cannot be made on procedures or objects or classes, so in some cases large searches must be performed. Oh well. Since class Storage is in the best position to obtain this information, and it has nothing else to do after startup anyways except swap objects to and from disk and answer capability questions, associations will be stored on the MOZs Storage object. As can be seen in the MozBase def, all objects have a stateless NewName associated with them, which is set at object creation using a name created by Storage, and passed to `init' on the object. class Storage has a dictionary to associate this name with the actual objects: 'oznameObj'. Since there's no real point worrying about making up file names for objects, and since I'd like to not require uniqueness of object 'names' (distinct from the Oz-level name for the object), I decided to just have the object files be named with incremental integers with a .obj suffix. A dictionary 'fileOzName' associates these integers with the unique object names. It would have been an array, but arrays in Oz can't grow without rather a lot of work. A dictionary 'oznameFile' provides the reverse mapping. Objects are stored as records (using `toRecord' and `fromRecord'). One of the things that is in those lists is the class identifier, which we might as well have be a name. That name must be unique. The file name the classes are stored in is just that name with a .class suffix. The file does _not_ store a pickle of the class: it stores the actual source of the class. Otherwise it would be very difficult to present users with the source of classes, since Oz does not appear to have a decompilation feature. The association between class names and the classes themselves are handled by the dictionary 'nameToClass'. It is useful in the extreme to store on Storage what class each object belongs to, so that we can certify objects as being instantiations of a known-safe class. So, at load time and during upgrades the class of any given object is store in the dictionary 'oznameToClassName'. Also, for wizardly purposes it is necessary for the Storage object to store a list of the capabilities available on each object in the MOZ. This are updated at load and upgrade time, and are stored in the dictionary 'oznameToCapabilities'. It would be incredibly inneficient to actually trace these dictionaries for every call to every method not on the local object, and it would make things much more complicated when dealing with objects from another MOZ *Note R1::, so these associations are, for the most part, only used at load and save time. `toRecord', `fromRecord' and upgrade use these assocations, and that's pretty much it, except for Wizardly usage, which is not discussed here.  File: design.info, Node: MOZ Records, Next: Security In Detail, Prev: Storage Issues, Up: Top MOZ Records *********** There are several home-grown record formats that are important to the MOZ, and they are specified here. * Menu: * object records:: The object record type. Used to pass around object wrapper and capability information. * verbs records:: The record used to store verbs for objects, including dealing with localization issues. * string records:: The record used for output string localization.  File: design.info, Node: object records, Next: verbs records, Up: MOZ Records object records ============== The object record type is fairly simple: object( wrapper:

ozName: capabilities: capabilities( init: start: stop: ... ) ) It just holds the active object wrapper, the ozName associated with the object, and the capability list. The only reason for the ozName field is that having that information handy is amazingly Useful for storing the object record to disk. An example of how to make an object record, from within an object, for giving to another object(1): object( wrapper: self.wrapper ozName: @ozName capabilities: capabilities( createObject: self.capabilityDict.createObject objectRefFromRecord: self.capabilityDict.objectRefFromRecord getClass: self.capabilityDict.getClass upgradeObject: self.capabilityDict.upgradeObject ) ) ---------- Footnotes ---------- (1) This really belongs in the programmer's manual.  File: design.info, Node: verbs records, Next: string records, Prev: object records, Up: MOZ Records verbs records ============= Verbs records are rather complex: allVerbs( en: verbs( get: verb( method: get parses: [ exampleParseName( leftMostPreposition: lmp( prep: [ 'from' 'out of' ] leftSide: lhs( objectName: getObject ) rightSide: rhs( objectName: fromObject ) ) ] ) ) ) lb: verbs( cpacu: verb( method: get parses: [ exampleParseName( objectName: getObject objectName: fromObject ) ] ) ) ) The outermost record is rather like the strings record. Each language's sub-record contains one field for each known verb. Each verb record contains the method that verb is associated with and a list of parse records(1), which contains instructions for parsing the user input (less the first word, which is always the verb) and turning it into arguments that the verb method will understand. The verb record is used as an index on a dictionary which is used to store the parsing we have done and to make sure that we don't parse the same input the same way more than once if we can avoid it. The example above would be appropriate on an object with a method like this: meth get( getObject: GetObject fromObject: FromObject ) ... end ---------- Footnotes ---------- (1) The parse record part of the verb records is in such a drastic state of flux right now that I'm not even going to try to document it. It eventually needs its own section, but that might belong in the programmer's manual.  File: design.info, Node: string records, Prev: verbs records, Up: MOZ Records string records ============== String records are quite simple: string( en: "This is the initial room. It is very boring." lb: "ti pamoi kumfa .i .a'ucu'i to'e cinri" ) As you can see, one field for each supported language, with a string in the field. Nothing special.  File: design.info, Node: Security In Detail, Next: Unsorted, Prev: MOZ Records, Up: Top Security In Detail ****************** * Menu: * Initial Capabilities:: An explanation of the basic capabilities that every object is created with. * Getting More Capabilities:: A discussion of how an object goes about getting capabilities beyond the initial set. * Persistence:: For capabilities to really work, the system has to be persistent. This should come as no surprise whatsoever to people used to MUDs, as it's standard, but it's discussed briefly anyways. * Capability Implementation:: A description of the structures and code used to implement capabilities in MOZ. * Wizardry:: Being a Wizard turns out to be an amazingly simple thing from the MOZ server perspective, and in fact is the only reason that Storage saves capability information.  File: design.info, Node: Initial Capabilities, Next: Getting More Capabilities, Up: Security In Detail Initial Capabilities ==================== Note that this section is quite likely to vary depending on the design of the specific MOZ in question. What is described here is simply from the perspective of the base MOZ server. In general, a created object should be passed, as an `init' argument, the smallest number of capabilities possible. At a minimum, this consists of the following: * A list of capabilities on the Storage object, so that new objects can be created. This would include a way to list class names as well as a direct object creation call. * A class testing capability from the Storage object. This takes an object reference and returns true if and only if the object in question is an instance of one of the built-in or otherwise judged safe Oz classes. This is used to warn players that they might be in trouble if they go into, say, rooms of other classes. Note that Wizards can add to this list, and should only do so if they've read the code in question. * A listContents capability on the room the object is created in. Note that this returns a list of requestCapability capabilities on the contents, i.e. a set of functions that can be used to ask for other capabilities * A listExits capability on the room the object is created in. * A requestCapability capability on the room the object is created in. That's a pretty short list, but it is actually quite sufficient, as we'll see in the next section. Note that this glosses over some issues, like the capabilities shared between the Player, Parser, and Connection objects, but that's a little low level for this discussion. Note that the requestCapability capability on the starting room allows the object to get the other capabilities on the starting room that it starts with; they are provided initially simply to save time.  File: design.info, Node: Getting More Capabilities, Next: Persistence, Prev: Initial Capabilities, Up: Security In Detail Getting More Capabilities ========================= Note that this section is quite likely to vary depending on the design on the specific MOZ in question. This is simply from the perspective of the base MOZ server. So, you're a newly created player in a MOZ, with the capablities listed in the previous section. You (or rather, the code underlying the actions) want to say something. Here are the steps involved: * Call listContents on the current room. This returns a list of requestCapability capabilities for all objects in the current room. * Call each of those capabilities, requesting a 'tell' capability. Some of them will refuse. That's fine; if they don't want to talk to you that's their business. * Run the tell capability on each of them with what it is that you have to say. Note that talking is _not_ handled by the room, in contrast to most MUDs. This is because in a capability based system, we want to avoid testing as much as possible, the testing in this case being whether the object wants to hear your tells or not. This way you can cache the capabilities for all the objects in the room you're in, so the test only gets run once (when you request the capability). Note, again, that certain things are glossed over, like retries for failed cached tell calls: the object may have revoked that capability, but may not mind giving it to you again. Note further that although there are capabilities given to you for every object in the room, this gives you no information other than that an object is there; not even the name of the object or any description information can be given to you if the object does not allow it (unless the room has been coded to 'cheat' and store capabilities it is passed and give them away freely). This is how 'invisible' objects can be implemented, although you can still inform the user that an 'invisible' object seems to be in the room. Another example: you want to move to a roomed joined to this one by an exit. * Call the listExits capability, use the capabilities provided to request name capabilities, check the names, figure out which exit you want. * Request the use capability on the exit. If you don't get it, well, you can't go that way. * Call the use capability, pass it a capability on yourself allowing updating of your location information (i.e. the capablities on the current room). Note that the updater in question should test whatever it gets passed, to make sure the capabilities it was given actually work! * The exit should already have an update capability on the room it's attached to, which it uses to remove you from the room's contents list, and add you to contents list on the other side. It then calls your update capability with the information from the new room. Any error causes everything to be changed back. * Revoke the update capability you passed the exit, stopping further updates. In fact, the update capability shoud do this itself. There is one other, rather different, way to get new capabilities: creating a new object returns a list of all available capabilites on that object.  File: design.info, Node: Capability Implementation, Next: Wizardry, Prev: Persistence, Up: Security In Detail Capability Implementation ========================= Capabilities are normally going to be stored in records on various attributes. The format is going to be: capabilities( foo: bar:), where foo and bar are methods on the object. Theses lists will be seperate from the attribute holding the reference to the object wrapper procedure. So, for example, the list of exits from the current location would be retrieved as follows: {@location.wrapper @locationCapabilities.listExits listExits($)} Note that this means there are two attrs for every capability list. When that request for listExits comes in, the object wrapper selects the appropriate method from its dictionary which is keyed on the Oz names of the methods, and calls that method. This way of doing things unfortunately breaks the abstraction of treating the object wrapper as though it were just another object. It was possible to make it much cleaner, but it made saving objects a gigantic pain and added a bunch of code. There were other ways of handling it without those drawbacks, but they all put a fair bit of knowledge burden on the coder in terms of understanding how Oz constructs records and such. If you really want to make a function that returns a function that acts just like a normal object (which I don't particularily recommend, by the way: it'll probably slow things down a bit), here's what one would look like: proc {CapProcMaker ObjProc CapList ?CapProc} proc {CapProc Meth} {ObjProc CapList.{Label Meth} Meth} end end Note that it is very important that you not try to save this procedure on your object (i.e. do not add it to the exports variable of the class), as this will cause the saving to fail. This means that if you are storing this procedure on an attr you need to check for its existence (probably using IsDet or Value.type) pretty much every time you use it, or make sure it gets recreated every time your object is reloaded (i.e., create it in your `start' method).  File: design.info, Node: Persistence, Next: Capability Implementation, Prev: Getting More Capabilities, Up: Security In Detail Persistence =========== Persistence is something that is entirely normal in MUDs, so this whole section may seem amazingly obvious to those of you used to MUD programming. However, it is very _abnormal_ in operating systems, but see EROS (http://www.eros-os.org/). In an operating system, if you want to open a file, you go and get a list of files, then ask for permission to open it. This makes it easy to deal with reboots: you just go look for the file again. Obviously, this can't work in a capability system: where do you get the list of files from? In the context of a MUD, there would need to be some way to search through all the rooms to find the one you were in to ask for capabilities on that room. There is no such ability. OK, that's not quite true, the Storage object can, and does, have such an ability, but _only_ for purposes of implementing persistence. So, the mud simply stores everything. Every few minutes, each object is written to disk, along with all the capabilities it has acquired. So there is no rebooting, really: after a crash or whatever, the MOZ is brought back up as though nothing had changed. Actually, that may not be quite true: only the capabilities that are saved as part of running `toRecord' on that class are stored, so stuff that is only being stored in running code or cache attrs is not preserved. Capabilities, because they are simply implemented as a record of Oz names, are stored as per normal by the Storage object. The object wrapper handles are turned into Oz names on the way out and converted back on the way in. For that to work, the newly loaded object that the capability is for must remeber what the names were that it had associated with various methods, especially since this is handled in the wrapper. The way that this is done is to simply have the information be stored as a dictionary in the wrapper; the wrapper has code in it to intercept the `toRecord' call and add the capability information, and also to intercept the `fromRecord' call and extract it again.  File: design.info, Node: Wizardry, Prev: Capability Implementation, Up: Security In Detail Wizardry ======== It would be possible to have a capability based system with no user having more power than any other user, in other words no Wizards, no Gods, no root. Of course, because of the nature of capabilities there's nothing to stop a user from setting up their own little domain in which they have ultimate power, but that's not the same. However, as a design decision I feel that that's a bad idea. Capabilities, as previously stated, make it rather easy for users to screw up if they try their hand at programming, or go into an area owned by a malicious user and say 'yes' to a lot of request messages and end up in a really bad way. I would like there to be a way for them to get un-screwed. Therefore, MOZ supports Wizardry by default. Being a Wizard is very simple: you have full capabilities on the Storage object, always has the most up-to-date set of capabilities for every object in the MOZ. Note that as the wrapper code is just about the only ex-DB code, it is impossible to circumvent the fact that the wrapper code will report capability changes to the Storage object. Note that users can get around this by creating objects which they do not report to the server. This has the substantial disadvantage that they will be lost upon server restart, as normal users are not given capabilities to anything that would allow them to store the information. Also, the wrapper tells Storage to update this information on each change.  File: design.info, Node: Unsorted, Prev: Security In Detail, Up: Top Unsorted ******** * As a stylistic issue, all methods should use full record field names for their arguments. * I am currently of the belief that all special user capabilities should be stored on objects (i.e. keys and such), primarily because any other method is just too damned easy to spoof. Here's some stuff about another way of doing it. * It is useful to have capablities on objects that are, more or less, automatically activated (i.e. a door that only you can walk through). MOZ calls such capabilities 'keys'. * Keys are stored on their own attribute on the user. * When a verb is call, a requestKey method is passed. Actually passing a key requires asking the user first. * Keys are in a dictionary indexed by ozName; ideally there would be some way to verify the ozName of the calling object. * However, having special capabilites on objects (which are presumably carried) leads to some issues with things like a dragon who doesn't eat anyone wearing the Gauntlet Of Might, or whatever. Ways of handling this are below. I have not picked yet, although I have a bit of a preference for a 'wear' verb. * Verb transparency to objects in inventory, which could cause problems if you pick up the wrong thing. But this could be selectable, i.e. some objects are allowed to be verb-trasparent. The easiest way would be a 'wear' command, such that objects that are word are checked for verbs. * Extensible verb lists (i.e. an attr?) in which an object field is part of the verb definition. This way the Gauntlet of Might would add a verb to the *player* (with permission, of course) called 'challengedBy'. Calling challengedBy( arg1: ) returns a value that causes the dragon to calm down. * This was in the class Parser section. I was smoking crack. Note that the verb call can immediately thread off, and should almost always do so if any non-trivial computation is done, since output is handled by an Oz port. This will be particularily important when in-game thread management is implemented; threading off will allow the player to kill threads if necessary, as commands can still get through. There is no way to get a list of threads in Oz, so what needs to be done at some point is to hack the parser object to thread off all verbs and save their thread ids on the player object, so that the player can kill them. * I have no idea where to put this. Movement of an object is three seperate things, in terms of capabilities: changing of where the object's location attr points, and changing the contents list of the before *and* after objects. Therefore, capabilities on three objects are involved. This should be discussed, both as a capability example, and an implementation detail. Maybe it's time for implementation notes?  Tag Table: Node: Top356 Node: Requirements2817 Ref: R13122 Ref: R23180 Ref: R33242 Ref: R43292 Ref: R53332 Ref: R63423 Ref: R73551 Ref: R83603 Ref: R93644 Ref: R103667 Ref: R113692 Ref: R123785 Ref: R133808 Node: Security Overview3888 Node: One-Instance Classes9035 Node: Ex-DB Design10610 Node: The moz Program12694 Node: The new_moz Program13442 Node: The Parsing Module13838 Node: The EnhancedString Module14469 Node: The Active Object Wrapper14853 Node: The MozCompilation Module18231 Node: Object References19198 Node: Class Definitions20021 Node: General Class Issues21778 Ref: General Class Issues-Footnote-130036 Ref: General Class Issues-Footnote-230263 Node: The Class Hierarchy30515 Node: class MozBase32169 Node: class Storage32729 Node: class Server33521 Node: class Described34065 Node: class Located34368 Node: class Mobile34638 Node: class Location34874 Node: class Player35133 Node: class Wizard35484 Node: class Puppet35854 Node: class Container36150 Node: class Exit36391 Node: class Gate37675 Node: class Terminus38047 Node: class Connection38390 Node: class Parser38935 Node: class Control39810 Node: class ClassControl40633 Node: Storage Issues40997 Node: Loading And Unloading41463 Node: Associations43157 Node: MOZ Records46601 Node: object records47146 Ref: object records-Footnote-148247 Node: verbs records48303 Ref: verbs records-Footnote-149873 Node: string records50102 Node: Security In Detail50491 Node: Initial Capabilities51387 Node: Getting More Capabilities53402 Node: Capability Implementation56757 Node: Persistence58916 Node: Wizardry61113 Node: Unsorted62686  End Tag Table