A Formalism for Internet Information References

"Daniel W. Connolly" <connolly@hal.com>
Errors-To: listmaster@www0.cern.ch
Date: Fri, 15 Apr 1994 23:03:10 --100
Message-id: <9404152045.AA04760@ulua.hal.com>
Errors-To: listmaster@www0.cern.ch
Reply-To: connolly@hal.com
Originator: www-talk@info.cern.ch
Sender: www-talk@www0.cern.ch
Precedence: bulk
From: "Daniel W. Connolly" <connolly@hal.com>
To: Multiple recipients of list <www-talk@www0.cern.ch>
Subject: A Formalism for Internet Information References
X-Listprocessor-Version: 6.0c -- ListProcessor by Anastasios Kotsikonas
Content-Length: 18295

Here's something that's been brewing in my mind for a couple years now.
It's still fairly rough, but it hangs together, and I'd like to see it
form a basis for disussing things like URNs and such:

A Formalism for Internet Information References

Daniel W. Connolly
$Id: formalism.txt,v 1.4 1994/04/15 20:30:56 connolly Exp $
http://www.hal.com/users/connolly/drafts/formalism.txt

ABSTRACT
========

This is a mathematical model for references between typical
information objects in the internet community; specifically, the model
covers the URL concept from the World Wide Web application and the
external body mechanism from MIME.

I suggest that this model be used to define the requirements for
specifications of new URL schemes and MIME access types, and that it
provides basis for other extensible reference mechanisms such as the
URN and URC mechanisms as variously proposed in the URI working group.

INTRODUCTION
============

We begin with definitions of some commonly used notions:

The set of octets, or bytes:

	Octet = { 0, 1, 2, ..., 255 }

Strings, sequences, or streams of bytes:

	Using the notation SEQ{S}, short for
		{ seq | for some N, seq : {0, 1, 2, ... N}  -> S }
	OctetStream = SEQ{Octet}

Then we formalize some of the MIME notions:

	ContentType = { text/plain, application/octet-stream, ... }
		/* just a finite set. Properties or the elements,
		aside from identity, are not defined */

	Entity = ContentType x OctetStream

And finally, we begin with a function to model the resolution of
references between entities in terms of an as-yet undefined set of
References:

	Resolve : Reference -> Entity

While in general, internet resources are do not all fit the definition
of Entity (telnet servers, ftp directories, etc.), the information
units transferred between information clients and servers generally
do. Thus we begin by discussing references to entites, and we define
references to other sorts of resources in terms of the basic Resolve
function.

For example, the basic model of computation for a WWW client goes:

	1. Given some reference r0, compute e0 := Resolve(r0)
	by sending r0 to the server and getting e0 back.
	2. Present e0
	3. Let the user choose a reference r1 from e0
		(click on an anchor, select a number, drag-select
		a range of text, enter some search words, etc.)
	4. Compute e1 := Resolve(r1) as above
	5. Iterate steps 2 thru 4


THE BASIC WEB "GLOBAL FILESYSTEM" REFERENCE
===========================================

The WWW reference mechnism, the URL, is a generalization of a
filesystem pathname.

	Dirs <: SEQ{OctetStream} /* <: meaning subset */
	File <: OctetStream

A normal filesystem pathname doesn't satisfy the properties of a
reference for several reasons: The pathname "x/y/z" may reference
different entities depending on the "current directory" of the client
process. So the relation:

	Dirs x Filename x Entity

is not a function. This can be addressed by using full pathnames. But
the path "/etc/motd" references different entities on different hosts.
So the WWW application adds hostname information to the path to make
it globally unique.

	Host <: OctetStream /* internet host names */

But even "local-file://host/dir/file" can reference different entities
at different times. But if we include a time argument

	Time = { real numbers } /* time in seconds since start of
				C.E. calendar, written here in
				ISO time format: YYYYMMDDHHMMSSZ.
				For the purposes of this discussion,
				a one second clock resolution is
				assumed to be sufficient */

then we have a well defined function:

	LOCAL-FILE.GET : (Host x Dirs x File) x Time -> OctetStream

Many filesystems don't associate an explicit ContentType with every
file. But we assume that the following function is well-defined:

	Resolve.LOCAL-FILE : Host x Dirs x File x Time -> Entity

For example, we can require that the content type be part of the
reference, or we can use heuristics guided by the filename. So with a
content type ct in hand, we have

	Resolve.LOCAL-FILE(h, d, f, t) = (ct, LOCAL-FILE.GET((h, d, f), t))

Or with a content type heuristic

	InferType : File -> ContentType

we can have

	Resolve.LOCAL-FILE(h, d, f, t)
		= (InferType(f), LOCAL-FILE.GET((h, d, f), t))

The local-file: access type shares semantics so far with the anon-ftp:
access type. In general, the ftp RETR operation can be modelled as:

	User <: OctetStream
	Account <: OctetStream u { {} } /* account is optional */

	FTP.GET : (Host x User x Account x Dirs x File) x Time -> OctetStream

Note 1: we don't consider the password, since the retrieved entity does not
vary as a function of the password

Note 2: we don't deal with type ASCII versus IMAGE transfers here
either. I don't think has significant impact on the model, but @@ I
need to think about it some more...

The anon-ftp: access type is a simplification of FTP that obviates the
User and Account parameters:

	ANON-FTP.GET : (Host x Dirs x File) x Time -> OctetStream
where
	ANON-FTP.GET((h, d, f), t) = FTP.GET((h, "anonymous", {}, d, f), t)

And gopher is similar:

	Port = { 0, 1, 2, ... ,65535 } /* IP port number */
	Selector = SEQ{Octet - { 9, 13 }} /* no tabs or newlines allowed */
	Search <: OctetStream u { {} } /* @@ what subset? */
	GType < ContentType /* Gopher types: subset of MIME types */

	GOPHER.GET : (Host x Port x Selector) x Time -> OctetStream

	Resolve.GOPHER : Host x Port x Selector x Search x GType x Time
		 -> Entity
where
	Resolve.GOPHER(h, p, sel, ser, gt, t) =
		(gt, GOPHER.GET((h, p, sel, ser), t))

The HTTP 0.9 model is nearly identical to gopher:

	Path = SEQ{ { 33 .. 126 } } /* no whitespace or 8-bit chars */
	HTTP0.GET : (Host x Port x Path x Search) x Time -> OctetStream
	Resolve.HTTP0 : Host x Port x Path x Search x Time -> Entity
where
	Resolve.HTTP0(h, p, path, ser, t)
		= (text/html, HTTP0.GET((h, p, path, ser), Time))

The HTTP 1.0 model is significantly more complex. We'll get to that
later. But in general, for a set Location* and a set Context*, if there
are functions g and t and a set s where:

	g : Location* x Context* -> OctetStream
	t : Location* x Context* -> ContentType
and
	For all c in Context* and l in Location*,
	Resolve((s, l, c)) = (t(l, c), g(l, c))

then we say that g is a GET function for Context* and Location*, t is
a TYPE function for them, and s is a SCHEME for them.

So far we have that LOCAL-FILE.GET is the GET function for Context* =
Time and Location* = Host x Dirs x File, ANON-FTP.GET is a GET function
for the same set -- hence the need for the distinguishing scheme.

We now have some subsets of the set of References. If s is a SCHEME
for a set Context* and a set Location*, with corresponding GET* and
TYPE* functions, then

	For all c in Context*, l in Location*
	(s, l, c) is an element of Reference.
since
	Resolve((s, l, c)) =  (TYPE*(l, c), GET*(l, c))

Here we define several current URL schemes in this manner, and I
suggest that in order to introduce a new URL scheme, we required a
definitions its Location and Context sets along with definitions of
GET and TYPE functions.

LOCAL-FILE:
	/* defined somewhat informally, as this is defined in terms
	of the local host */
	SCHEME = "local-file"	(a string is an OctetStream, which is a set)
	Location = Host x Dirs x File
	Context = Time x (ContentType u { {} })
	GET((h, d, f)) given informally:
		FILE *fp = fopen(path(d,f)); fread(fp...);
	TYPE((h, d, f), (t, ct)) := if ct = {}, InferType(f)
					else ct


FTP:
	SCHEME = "ftp"
	Location = Host x User x Account x Dirs x File
	Context = Time x (ContentType u { {} })
	GET((h, u, a, d, f)) as per FTP RFC:
		CONNECT h
		USER u; PASSWD ...
		Account a  /* unless a is {} */
		CWD d1
		CWD d2	/*@@ how many CWDs? */
		...
		RETR f	/*@@ type i/a command? */

	TYPE((h, u, a, d, f), (t, ct)) := if ct = {}, InferType(f)
					else ct
	
ANON-FTP:
	SCHEME = "anon-ftp" /* @@ sometimes written ftp */
	Location = Host x Dirs x File
	Context = Time x (ContentType u { {} })
	GET((h, d, f)) as per FTP RFC:
		CONNECT h
		USER anonymous; PASSWD <client's email address>
		CWD d1
		CWD d2	/*@@ how many CWDs? */
		...
		RETR f	/*@@ type i/a command? */

	TYPE((h, d, f), (t, ct)) := if ct = {}, InferType(f)
					else ct

GOPHER:
	SCHEME = "gopher"
	Location = Host x Port x GType x Selector x Search
	Context = Time
	GET((h, p, gt, sel, ser)) as per Gopher Protocol:
		telnet h:p
		if ser = {}, send sel
		else send sel<tab>ser

	TYPE((h, p, gt, sel, ser), t) = gt /* @@ other type mechansims? */

HTTP:
	SCHEME = "http"
	Location = Host x Port x Dirs x File x Search
	Accept = { f | f : ContentType -> Real }
	Context = Time x Accept
	GET((h, p, d, f, s), (t, a)) and
	TYPE((h, p, d, f, s), (t, a)) in terms of HTTP GET operation:
		compute path from d, f, s
		compute accept encoding from a
		telnet to h:p
		send:	GET path HTTP/1.0
			Accept: <accept encoding>
			As-Of: t	/* @@ new HTTP header. Version? */

@@ forms in HTTP in MAILTO; MIME MAIL-SERVER access type
@@ wais, prospero, finger?


RELIABILITY ISSUES WITH FILE (AKA MUTABLE OCTET-STREAM OBJECT) REFERENCES
=========================================================================

The following problem of references to mutable objects is sharted by
several schemes: Suppose an author discovers a file at 1pm and
constructs an HREF to that file; then, the file is modified at 2pm,
and finally a reader resolves the reference at 3pm: the entity the
reader sees is different from what the author saw. That is,

	GET(loc, tread) != GET(loc, tauthor)
and hence
	Resolve(loc, tread) != Resolve(loc, tauthor)

But in many cases, the author and the reader _can_ read the file at
different times and get the same result. How can we determine if this
is the case?

One way is to use the time of last modification of the file. We can
model this as a function lastmod satisfying:

	lastmod : Location* x Time -> Time
where
	For all l in Location*, t1, t2 in Time
	If t1 >= lastmod(l, t2)
	then GET*(t2)(l) = GET*(t1)(l)

Where GET* is a GET function for Location and Time.  We'll call such a
function a LASTMOD function for the set Location* and the function
GET*.

Note 1: I don't believe the FTP RFC specifies how to determine the
time of last modification, but it can be determined reliably in many
cases. @@ I need to look into this...)

Note 2: I don't believe Gopher specifies a way to determine the time
of last modification. Perhaps Gopher+ does.

Then, in the above example, we have the following:

	If tauthor >= LASTMOD(loc, tread)
	Then GET(loc, tread) = GET(loc, tauthor)
and hence
	Resolve((s, loc, tread)) = Resolve((s, loc, tauthor))

That is, if the reader can determine that the file hasn't changed
since the link was made, s/he can have confidence in the integrity of
the link. If the mod time is later, the client software can warn the
reader before transferring the possibly erroneous data.

So one way to enhance the typical HREF="local-file://host/dir/file" to
be a well-defined Reference is to determine the time context of the
reference.  This can ben done in any of several ways:

	1. Enhance local-file URLs to contain time info; e.g. the author
	writes:

		local-file://host/dir/file;time=19940414141000Z

	2. Enhance the HTML A element to contain time info; e.g.:

		<A HREF="local-file://host/dir/file" TIME="19940414141000Z">

	3. If the reference is in a file whose modification time is known,
	we can make the heuristic assumption that all the references
	in the file were current as of the last modification of the
	file.

Either of the first two is tedious for hand-edited documents. But they
are simple to implement in any cut-n-paste or "paste link" feature.
They also work nicely to fill in the missing content type.

	<A HREF="ftp://host/dir/file;user=connolly;account=general"
		date="19940414141000Z"
		content-type="application/postscript">

The third option is more convenient, though less robust. Consider:

	1pm: file Y written with info on apples
	2pm: file X says "see file Y for more on apples"
	3pm: file Y modified to have info on oranged rather than apples
	4pm: file X modified, but still says "see Y for apples"
	5pm: reader of X cannot detect (by machine) that
		the link to Y is corrupt.

Also: references get copied and pasted, quoted, etc. The time context
of the reference should travel with the reference.

Another technique for detecting faults in resolving references is to
include the MD5 signature of the entity in the reference. This
mechanism is somewhat expensive: it only detects faults _after_
transmission of the octet stream, and it requires computation
proportional to the length of the entity.

A much less reliable technique is to include the octet count of the
entity in the reference. The FTP RFC includes a mechanism to detect
this type of fault before transmission of the entity. (i.e. you can
ask an FTP server how big a file is before you transfer it.)


STRING REPRESENTATIONS OF REFERENCES
====================================

The string representation of a reference is an important part of the
specification of various applications. But it is not clear that we
need one string representation for all applications. It is much more
critical that we understand the properties of the objects we are
trying to represent. Once we have a definition of the set of object
we're representing, we can choose various means to represent the set
in various contexts, with well-defined mappings between syntaxes, for
example, between W3's HREF="xxx" and MIME's external-body; access-type="xxx" 
syntaxes.

A definition of a scheme syntax may include incomplete string
representations, as long as it discusses mechanisms and/or heuristics
to determine the remaining parts of a well-defined reference.

LOCAL-FILE:
	string rep((h, d, f), (t, ct)) given in perl as
		&file_uri("local-file", $h, *d, $f, $t, $ct)
		where d is a list of the directories
		and t is in ISO time format

sub file_rep{
    local($scheme, $host, *dirs, $file, $time, $content_type) = @_;
    local($sep, $ret);

    if(@dirs){ $sep = "/"; }

    grep($_ = &uri_escape($_), @dirs);

    $ret = $scheme . "://"
	. &uri_escape(host)
	    . "/" . join('/', @dirs)
		. $sep . $file;

    if($time){
	$ret .= ";as-of=" . $time; # @# better name for as-of?
    }
    if($content_type){
	$ret .= ";content-type=" . &uri_escape($content_type);
    }

    return $ret;
}

sub uri_escape{
	local($_) = @_;
	s-[/?=;:]-sprintf("%%%02X", ord($&))-ge; # list of illegal chars?
	return $_;
}

FTP:
	string rep((h, u, a, d, f), (t, ct)) given in perl as
		&file_uri("ftp", "$u@$h", *d, $f, $t, $ct) # @@ account?
		where d is a list of the directories
		and t is in ISO time format
	NOTE: the string representation is often written without the
	time and content-type information. In those cases, the content
	type should be inferred from the filename and the time should
	be inferred from the context of the reference.

ANON-FTP:
	string rep((h, d, f), (t, ct)) given in perl as
		&file_uri("anon-ftp", $h, *d, $f, $t, $ct)
		where d is a list of the directories
		and t is in ISO time format
	NOTE: the string representation is often written without the
	time and content-type information. In those cases, the content
	type should be inferred from the filename and the time should
	be inferred from the context of the reference.

GOPHER:
	string rep((h, p, ty, sel, ser), t) given in perl as
		&file_uri("gopher", $p == 70 ? $h : "$h:$p", (),
			 &gtype_char($ct) . $sel, $t)
			 . $ser ? "?$ser" : ""
		where d is a list of the directories
	NOTE: the string representation is often written without the
	time information. And unfortunately, there are no mechanisms
	for determining whether gopher references with different times
	refer to the same entity. @@md5 mechanism

HTTP:
	string rep((h, p, d, f, s), (t, a)) given in perl as
		&file_uri("http", $p == 80 ? $h : "$h:$p", *d,
			 $f, $t, max(a))
			 . $s ? "?$s" : ""
		where d is a list of the directories t in ISO time format
	NOTE 1: the string representation is often written without the
	time. In these cases, the time should be inferred from
	context.

	NOTE 2: the string representation can only represent a limited
	form of the content type metric function, i.e. "type x is
	best," and ofthen it is ommitted completely. In those cases,
	the client should encode a content type metric function based
	on its capabilities and the user preferences.

RELATIVE NAMES
==============

Essentially (@@ need more discussion), if we have

	Resolve(r0) = e0

and a name n is found inside e0, we apply the function

	Relative : Name x Reference -> Reference

and compute

	Resolve(Relative(n, r0))

RELIABLE CACHING AND MIRRORING
==============================

Essentially (@@ need more discussion), if a client wants to
compute Resolve(r0), it can, for example, give the whole r0
reference to a proxy server s, ala:

	Resolve(r0) = Resolve((proxy, (s, r0)))

and so the question becomes: is Resolve(r0) in the cache?

In order to do mirroring, we need techniques to distribute information
about circumstances where a client can substitute references, i.e.
where
	Resoleve(r') = Resolve(r)
but r' is easier to get to.

This is where heirarchical namespaces come in: we'd like to distribute
information of the form:

	For all *,
	Resolve("ftp://host1/dir1/*") = Resolve("ftp://host2/dir2/*")


LOCATION-INDEPENDENT REFERENCES (URN's)
=======================================

Rather than:

	Resolve : Scheme x Location x Context -> Entity

we might as well write:

	Resolve : Scheme x Name x Context -> Entity

for example we can define:

	RFC.GET : Name x ContentType -> OctetStream
	RFC.FILENAME : Name x ContentType -> File
as
	RFC.FILENAME(n, c) = "n.txt" if c is text/plain,
				"n.ps" if c is application/postscript
	For all time t /* since rfc's don't change */
	RFC.GET(n, c) = ANON-FTP.GET(("ds.internic.net", "/pub/rfc",
					RFC.FILENAME(n, c)), t)


FUTURE DISCUSSION
=================

@@ security, scalability, reliability (fault detection and tolerance,
caching, migration, compression, encryption, authentication)


Daniel W. Connolly        "We believe in the interconnectedness of all things"
Software Engineer, Hal Software Systems, OLIAS project   (512) 834-9962 x5010
<connolly@hal.com>                   http://www.hal.com/%7Econnolly/index.html