Oberon/A2/Unix.Oberon.NetSystem.Mod

(* ETH Oberon, Copyright 2000 ETH Zürich Institut für Computersysteme, ETH Zentrum, CH-8092 Zürich.
Refer to the general ETH Oberon System license contract available at: http://www.oberon.ethz.ch/ *)

MODULE NetSystem IN Oberon;

IMPORT S := SYSTEM, IP IN A2, DNS := DNS IN A2, TCP := TCP IN A2,  UDP := UDP IN A2,
		Texts, Oberon, Input, Fonts, Strings, Out;

	CONST
		CR = 0DX; LF = 0AX; 

		(* res values *)
		done* = 0;	(*everything went ok*)
		error* = 1;	(*failure occured*)
		timeout* = 2;	(*opening a connection is timed out*)

		(* return values of procedure State *)
		closed* = 0;	(*connection is closed (neither sending nor receiving)*)
		listening* = 1;	(*passive connection is listening for a request*)
		in* = 2;	(*receiving only*)
		out* = 3;	(*sending only*)
		inout* = 4;	(*sending and receiving is possible*)
		waitCon* = 5; (** still waiting for connection *)
		errorCon* = 6; (** connecting failed *)
		undef = -1; (** unknown state *)

		IPAdrLen = 4;

		(* any port value *)
		anyport* = 0;

	TYPE

		IPAdr* = IP.Adr;

		Connection* = TCP.Connection; 	(* TCP-Connection *)

		HostInfo* = POINTER TO HostInfoDesc;  (** handle for asynchronous GetIP and GetName *)
		HostInfoDesc* = RECORD
			next: HostInfo;
			ip-: IPAdr; (** the ip-number of host name *)
			name-: ARRAY 64 OF CHAR; (** the host name for ip-number *)
			done-, err-, getip: BOOLEAN (** indicating success or failure *)
		END;

		Socket*= UDP.Socket; 	(* UDP-Connection *)

		Access = POINTER TO AccessDesc;
		AccessDesc = RECORD
			service, user, host, passwd: ARRAY 64 OF CHAR;
			next: Access
		END;
		
		Bytes = ARRAY MAX( SIGNED32 ) OF CHAR;

	VAR
		hostIP* : IPAdr;	(** the ip-number of host name *)
		anyIP*, allIP*: IPAdr;
		hostName*: ARRAY 65 OF CHAR;	(** own machine name *)

		hostInfos: HostInfo;
		W: Texts.Writer;
		accessList: Access;
		verbose: BOOLEAN;

	PROCEDURE Start*;
	VAR res: INTEGER;
	BEGIN
		DNS.GetHostName( hostName, res );
		IF res = DNS.Ok  THEN
			DNS.HostByName( hostName, hostIP, res );
		END
	END Start;

	PROCEDURE Stop*;
	BEGIN
	END Stop;

	PROCEDURE ToHost* ( CONST num: ARRAY OF CHAR; VAR adr: IPAdr; VAR done: BOOLEAN );
	BEGIN
		adr := IP.StrToAdr(num);
		done := ~(IP.IsNilAdr(adr))
	END ToHost;

	PROCEDURE ToNum*( adr: IPAdr; VAR num: ARRAY OF CHAR );
	BEGIN
		IP.AdrToStr(adr, num)
	END ToNum;

	PROCEDURE AsyncGetIP*( VAR hostInfo: HostInfo; name: ARRAY OF CHAR );
	VAR res: INTEGER;
	BEGIN
		NEW( hostInfo ); Strings.Lower( name, name );
		hostInfo.next := NIL; COPY( name, hostInfo.name );
		hostInfo.done := FALSE; hostInfo.err := FALSE; 
		IF (name[0] >= "0") & (name[0] <= "9") THEN
			DNS.HostByNumber( hostInfo.ip, name, res );
			hostInfo.err := res # DNS.Ok; hostInfo.done := TRUE
		ELSE
			DNS.HostByName( name, hostInfo.ip, res );	(* human comprehensible name *)
			IF res = DNS.Ok THEN
				hostInfo.err := FALSE; hostInfo.done := TRUE;
				hostInfo.next := hostInfos;  hostInfos := hostInfo
			ELSE
				hostInfo.err := TRUE
			END
		END;
	END AsyncGetIP;

	PROCEDURE GetIP* ( CONST name: ARRAY OF CHAR;  VAR IP: IPAdr );
	VAR hostInfo: HostInfo;
	BEGIN
		IP := anyIP;
		AsyncGetIP( hostInfo, name );
		IF ~hostInfo.err THEN
			IP := hostInfo.ip
		END
	END GetIP;

	PROCEDURE AsyncGetName*( VAR hostInfo: HostInfo; IP: IPAdr );
	VAR res: INTEGER;
	BEGIN
			NEW( hostInfo );
			hostInfo.getip := FALSE; hostInfo.next := NIL;
			S.MOVE( ADDRESSOF( IP ), ADDRESSOF( hostInfo.ip ), IPAdrLen );
			hostInfo.done := FALSE; hostInfo.err := FALSE; 
			DNS.HostByNumber( hostInfo.ip, hostInfo.name, res );
			IF res = DNS.Ok THEN
				hostInfo.err := FALSE; hostInfo.done := TRUE;
				hostInfo.next := hostInfos; hostInfos := hostInfo
			ELSE
				hostInfo.err := TRUE; hostInfo.name[0]:= 0X
			END
	END AsyncGetName;

	PROCEDURE GetName* ( IP: IPAdr; VAR name: ARRAY OF CHAR );
	VAR hostInfo: HostInfo;
	BEGIN
		COPY("", name);
		AsyncGetName( hostInfo, IP );
		IF ~hostInfo.err THEN
			COPY( hostInfo.name, name )
		END
	END GetName;

	(** Passwords *)

	PROCEDURE WriteURL( CONST service, user, host: ARRAY OF CHAR );
	BEGIN
		Out.String("NetSystem.SetUser ");  Out.String(service);
		Out.Char(":");  Out.String(user);  Out.Char("@");
		Out.String(host);  Out.String(" ~"); Out.Ln
	END WriteURL;

	(** Retrieve the password for user using service on host.  Parameters service, host and user can be specified.
	Parameter user is in/out.  If user is empty, the first (user,password) pair for host is returned.  Otherwise the
	password of the user is returned. *)

	PROCEDURE GetPassword*(service, host: ARRAY OF CHAR;  VAR user, password: ARRAY OF CHAR);
	VAR access: Access;  r: Texts.Reader;  ch: CHAR;
	BEGIN
		Strings.Lower(service, service);  Strings.Lower(host, host);
		access := accessList;
		WHILE (access # NIL) & ~((access.service = service) & (access.host = host) & ((user = "") OR (access.user = user))) DO
			access := access.next
		END;
		IF access # NIL THEN
			COPY(access.user, user);  COPY(access.passwd, password)
		ELSE
			IF (service # "") & (user # "") THEN
				IF Oberon.Log.len > 0 THEN
					Texts.OpenReader(r, Oberon.Log, Oberon.Log.len-1);
					Texts.Read(r, ch);
					IF ch # CHR(13) THEN Out.Ln END
				END;
				WriteURL(service, user, host);
			END;
			COPY("", user); COPY("", password)
		END
	END GetPassword;

	(** Remove access for user using service on host. *)
	PROCEDURE DelPassword*( CONST pservice, user, phost: ARRAY OF CHAR);
	VAR paccess, access: Access;
		service, host: ARRAY 64 OF CHAR;
	BEGIN
		Strings.Lower( pservice, service );  Strings.Lower( phost, host );
		paccess := NIL; access := accessList;
		WHILE (access # NIL) & ((access.service # service) & (access.host # host) & (access.user # user)) DO
			paccess := access; access := access.next
		END;
		IF access # NIL THEN
			IF paccess # NIL THEN
				paccess.next := access.next
			ELSE
				accessList := access.next
			END
		END
	END DelPassword;
	
	(* An accessString is delimited by whitespace or "~" or a non-font entity. *)
	PROCEDURE ReadAccessString(VAR R: Texts.Reader; VAR accessString: ARRAY OF CHAR; VAR at: SIGNED32): BOOLEAN;
	VAR i: SIGNED32;
	BEGIN
		i := 0; at := -1;
		REPEAT Texts.Read(R, accessString[i]) (* Skip leading whitespace. *)
		UNTIL R.eot OR ((R.lib IS Fonts.Font) & (" " < accessString[i]));
		WHILE ~R.eot & (R.lib IS Fonts.Font) & (" " < accessString[i]) & (accessString[i] # "~") DO
			IF accessString[i] = "@" THEN at := i END;
			INC(i);
			Texts.Read(R, accessString[i])
		END;
		accessString[i] := 0X;
		RETURN 0 < i
	END ReadAccessString;

	(* Extract next component from an accessString. *)
	PROCEDURE Next(VAR accessString, str: ARRAY OF CHAR; VAR a, at: SIGNED32): BOOLEAN;
	VAR i: SIGNED32;
	BEGIN
		i := 0;
		WHILE (accessString[a] # 0X) & (a # at) & (accessString[a] # ":") & (accessString[a] > " ") 
				& (accessString[a] # "/") & (accessString[a] # "~") (* & (R.lib IS Fonts.Font)*) DO
			str[i] := accessString[a];
			INC(i); INC(a)
		END;
		str[i] := 0X;
		RETURN 0 < i
	END Next;

	(* Accept a password from the user via the keyboard. *)
	PROCEDURE ReadPwd(VAR access: Access; VAR pwd: ARRAY OF CHAR; VAR ch: CHAR);
	VAR i: SIGNED32;
	BEGIN
		Texts.SetColor(W, 1);
		Texts.WriteString(W, "Mouse left click in Oberon to focus input.");Texts.WriteLn(W);
		Texts.WriteString(W, "Enter password for ");
		Texts.WriteString(W, access.service); Texts.WriteString(W, "://"); 
		Texts.WriteString(W, access.user); Texts.Write(W, "@");
		Texts.WriteString(W, access.host); Texts.WriteString(W, ": ");
		Texts.SetColor(W, 15);
		Texts.Append(Oberon.Log, W.buf);
		Input.Read(ch); i := 0;
		WHILE ch > " " DO
			IF ch = 7FX (* Delete *) THEN
				IF i > 0 THEN
					Texts.Delete(Oberon.Log, Oberon.Log.len-1, Oberon.Log.len);
					DEC(i)
				END
			ELSE
				Texts.Write(W, "*"); Texts.Append(Oberon.Log, W.buf);
				pwd[i] := ch;  INC(i)
			END;
			Input.Read(ch)
		END;
		pwd[i] := 0X;
		Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf)
	END ReadPwd;

	(* access points to a tetrad.  If a match to the (service, user, host) of access is found 
		in accessList, the password is replaced.  Otherwise access is prepended to accessList. *)
	PROCEDURE Update(access: Access);
	VAR a: Access;
	BEGIN
		IF verbose THEN
			Texts.WriteString(W, "Update of access list requested with ");
			Texts.WriteString(W, access.service);
			Texts.Write(W, ":"); Texts.WriteString(W, access.user);
			Texts.Write(W, ":");
			IF access.passwd # "" THEN Texts.WriteString(W, "<password>") END;
			Texts.Write(W, "@"); Texts.WriteString(W, access.host); Texts.WriteLn(W);
			Texts.Append(Oberon.Log, W.buf)
		END;
		a := accessList;
		WHILE (a # NIL) & ~((a.service = access.service) & (a.host = access.host) & (a.user = access.user)) DO
			a := a.next
		END;
		IF a = NIL THEN 
			IF verbose THEN
				Texts.WriteString(W, "Matching access not found.  Prepend access for "); 
				Texts.WriteString(W, access.service); Texts.WriteString(W, " to accessList.");Texts.WriteLn(W)
			END;
			access.next := accessList;  accessList := access
		ELSE
			IF verbose THEN
				Texts.WriteString(W, "Matching access found.  Copy password in."); Texts.WriteLn(W)
			END;
			COPY(access.passwd, a.passwd)
		END;
		Texts.Append(Oberon.Log, W.buf)
	END Update;

	(** NetSystem.SetUser {"\v" | access } "~".
		access = service ":" ["//"] user [":" password] "@" host [ "/" ].
		If password is not specified in-line, submit interactively.
		"@" can occur anywhere in an accessString and host begins after the last @.
		The (service, host, user, password) tetrad is stored in memory for retrieval with GetPassword. *)
	PROCEDURE SetUser*;
		VAR
			R: Texts.Reader;
			service, usr, host, pwd: ARRAY 64 OF CHAR; (* Components of an accessDesc. *)
			accessString: ARRAY 256 OF CHAR;
			c: SIGNED32; (* Index in accessString. *)
			at: SIGNED32; (* Index of the last "@" in the accessString. *)
			acc: Access;
			tempAccess: Access; (* Temporary list of accesses for which passwords are pending. *)
			ch: CHAR;
	BEGIN
		(* Accesses where the password is included in parameters. *)
		Texts.OpenReader(R, Oberon.Par.text, Oberon.Par.pos);
		tempAccess := NIL;
		WHILE ReadAccessString(R, accessString, at) DO
			IF accessString = "\v" THEN
				verbose := TRUE
			ELSE
				c := 0;
				WHILE Next(accessString, service, c, at) DO
					Strings.Lower(service, service);
					IF verbose THEN	
						Texts.WriteLn(W);
						Texts.WriteString(W, "service = "); Texts.WriteString(W, service); Texts.WriteLn(W)
					END;
					IF accessString[c] = ":" THEN
						INC(c);
						IF ~Next(accessString, usr, c, at) THEN
							Texts.WriteString(W, "For service = "); Texts.WriteString(W, service);
							Texts.WriteString(W, ", Next() failed to retrieve usr."); Texts.WriteLn(W)
						ELSE (* Found usr.  Look fo password. *)
							IF accessString[c] = "@" THEN
								IF verbose THEN	
									Texts.WriteString(W, "Password omitted.  Prompt for it later."); Texts.WriteLn(W);
								END;
								ASSERT(c = at);
								INC(c);
								IF ~Next(accessString, host, c, at) THEN
									Texts.WriteString(W, "For service = "); Texts.WriteString(W, service);
									Texts.WriteString(W, ", Next() failed to retrieve host."); Texts.WriteLn(W)
								ELSE
									Strings.Lower(host, host);
									NEW(acc);  acc.next := tempAccess; tempAccess := acc;	(* prepend to temp list *)
									COPY(service, acc.service); COPY(host, acc.host); COPY(usr, acc.user)
								END
							ELSIF accessString[c] = ":" THEN
								IF verbose THEN	
									Texts.WriteString(W, ": separator provided for password."); Texts.WriteLn(W);
								END;
								INC(c);
								IF ~Next(accessString, pwd, c, at) THEN
									Texts.WriteString(W, "For service = "); Texts.WriteString(W, service);
									Texts.WriteString(W, ", Next() failed to retrieve password."); Texts.WriteLn(W)
								ELSE
									IF verbose THEN
										IF pwd # "" THEN	
											Texts.WriteString(W, "Password supplied in SetUser command."); Texts.WriteLn(W)
										ELSE
											Texts.WriteString(W, ": separator present but password not in parameter of SetUser().");
											Texts.WriteLn(W)
										END
									END;
									IF accessString[c] = "@" THEN
										ASSERT(c = at);
										INC(c);
										IF ~Next(accessString, host, c, at) THEN
											Texts.WriteString(W, "For service = "); Texts.WriteString(W, service);
											Texts.WriteString(W, ", Next() failed to retrieve host."); Texts.WriteLn(W)
										ELSE
											IF verbose THEN
												Texts.WriteString(W, "host = "); Texts.WriteString(W, host); Texts.WriteLn(W)
											END;
											IF host # "" THEN
												NEW(acc); COPY(service, acc.service); COPY(host, acc.host);
												COPY(usr, acc.user); COPY(pwd, acc.passwd);
												Update(acc)
											END (* IF host # "" *)
										END (* ~Next(accessString, host, c, at) *)
									END (* IF accessString[c] = "@" *)
								END (* IF ~Next(accessString(accessString, pwd, c, at) *)
							END (* IF accessString[c] = "@" *)
						END (* ~Next(accessString, usr, c, at) *)
					END (* IF accessString = ":" *)
				END (* WHILE Next(accessString, service, c, at) *)
			END (* IF accessString = "\v" *)
		END; (* WHILE ReadAccessString *)

		(* Accesses where the password is requested interactively. *)
		WHILE tempAccess # NIL DO (* Fill password in each access of "temporary" list. *)
			ReadPwd(tempAccess, pwd, ch);
			IF ch = CR THEN	(* password was entered *)
				acc := tempAccess;
				tempAccess := tempAccess.next;
				COPY(pwd, acc.passwd);
				Update(acc)
			END
		END;
		Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf)
	END SetUser;

	(** clears accessList from memory *)
	PROCEDURE ClearUser*;
	BEGIN
		accessList := NIL
	END ClearUser;

(************************************** TCP ****************************************)

	PROCEDURE Available* (conn: Connection): SIGNED32;
	BEGIN
		RETURN conn.Available()
	END Available;

	PROCEDURE OpenConnection* ( VAR conn: Connection; locPort: UNSIGNED16; remIP: IPAdr; remPort: UNSIGNED16;
													VAR res: SIGNED16 );
	VAR r: SIGNED32;
	BEGIN
		IF remPort = anyport THEN remIP := anyIP END;
		NEW( conn );
		conn.Open( locPort, remIP, remPort MOD 10000H, r );
		IF r = TCP.Ok THEN res := done  ELSE  res := error  END
	END OpenConnection;

	PROCEDURE AsyncOpenConnection* (  VAR conn: Connection; locPort: UNSIGNED16; remIP: IPAdr; remPort: UNSIGNED16;
																 VAR res: SIGNED16 );
	BEGIN
		OpenConnection( conn, locPort, remIP, remPort, res )
	END AsyncOpenConnection;

	PROCEDURE CloseConnection* ( conn: Connection );
	BEGIN
		IF conn # NIL THEN conn.Close() END
	END CloseConnection;

	PROCEDURE Requested* ( conn: Connection ): BOOLEAN;
	BEGIN
		RETURN (conn.state = listening) & conn.Requested() 
	END Requested;

	PROCEDURE Accept* ( conn: Connection; VAR newC: Connection; VAR res: SIGNED16 );
	VAR r: SIGNED32;
	BEGIN
		conn.Accept( newC, r );
		IF r = TCP.Ok THEN res := done  ELSE  res := error  END
	END Accept;

	PROCEDURE State* (conn: Connection): SIGNED16;
	VAR res: SIGNED16;
	BEGIN
		CASE conn.state OF
		| TCP.Closed: res := closed
		| TCP.Listen: res := listening  
		| TCP.Established: res := inout
		ELSE
			res := undef
		END;
		RETURN res
	END State;

	PROCEDURE GetPartner* (conn:Connection; VAR remIP: IPAdr; VAR remPort: UNSIGNED16);
	BEGIN
		remIP := conn.fip;
		remPort := SHORT( conn.fport )
	END GetPartner;

	(*----- Read -----*)

	PROCEDURE Read* (conn: Connection; VAR ch: CHAR);
	VAR l: SIZE; r: INTEGER; bytes: ARRAY 1 OF CHAR;
	BEGIN 
		conn.Receive( bytes, 0, 1, 1, l, r );
		ch := bytes[0]
	END Read;

	PROCEDURE ReadBytes* ( conn: Connection; pos, len: SIGNED32; VAR buf: ARRAY OF S.BYTE );
	VAR l: SIZE; r: INTEGER;
	BEGIN 
		conn.Receive( S.VAL( Bytes, buf ), pos, len, len, l, r );
	END ReadBytes;

	PROCEDURE ReadBool* (conn: Connection; VAR b: BOOLEAN);
	VAR l: SIZE; r: INTEGER; bytes: ARRAY 1 OF CHAR;
	BEGIN 
		conn.Receive(bytes, 0, 1, 1, l, r );
		b := ODD(ORD(bytes[0]));
	END ReadBool;

	PROCEDURE ReadInt* ( conn: Connection; VAR x: SIGNED16 );
	VAR buf: ARRAY 4 OF CHAR; len: SIZE; res: INTEGER;
	BEGIN 
		conn.Receive( buf, 0, 2, 2, len, res ); 
		IF (res = 0) & (len = 2) THEN
			x := ORD(buf[0])*100H + ORD(buf[1])
		ELSE
			x := 0
		END
	END ReadInt;

	PROCEDURE ReadLInt* (conn: Connection; VAR x: SIGNED32);
	VAR buf: ARRAY 4 OF CHAR; len: SIZE; res: INTEGER;
	BEGIN 
		conn.Receive( buf, 0, 4, 4, len, res ); 
		IF (res = 0) & (len = 4) THEN
		x := ORD(buf[0])*1000000H + ORD(buf[1])*10000H + ORD(buf[2])*100H + ORD(buf[3])
		ELSE
			x := 0
		END
	END ReadLInt;

	(** Blocking read a string terminated by ( [CR]LF | 0X ). *)
	PROCEDURE ReadString* (conn: Connection; VAR s: ARRAY OF CHAR);
	VAR
		ch, ch0: CHAR;
		i, l: SIZE; r: INTEGER;
		bytes: ARRAY 1 OF CHAR;
	BEGIN
		i := -1; ch := 0X;
		REPEAT
			INC( i );
			ch0 := ch; 
			conn.Receive( bytes, 0, 1, 1, l, r );
			ch := bytes[0];
			s[i] := ch;
		UNTIL ( r # TCP.Ok) OR (ch = 0X) OR (ch = LF);
		IF (ch = LF) & (ch0 = CR) THEN
			s[i - 1] := 0X
		ELSE s
			[i] := 0X
		END
	END ReadString;

	(*----- Write -----*)

	PROCEDURE Write* (conn: Connection; ch: CHAR);
	VAR r: SIGNED32; bytes: ARRAY 1 OF CHAR;
	BEGIN
		bytes[0] := ch;
		conn.Send( bytes, 0, 1, TRUE, r )
	END Write;

	PROCEDURE WriteBytes* (conn: Connection; pos, len: SIGNED32; CONST buf: ARRAY OF S.BYTE );
	VAR r: SIGNED32;
	BEGIN 
		conn.Send( S.VAL( Bytes, buf ), pos, len, TRUE, r)
	END WriteBytes;

	PROCEDURE WriteBool* (conn: Connection; b: BOOLEAN);
	VAR r: SIGNED32; bytes: ARRAY 1 OF CHAR;
	BEGIN
		IF b THEN bytes[0] := 1X ELSE bytes[0] := 0X END;
		conn.Send( bytes, 0, 1, TRUE, r )
	END WriteBool;

	PROCEDURE WriteInt* (conn: Connection; x: SIGNED16);
	VAR buf: ARRAY 2 OF CHAR; r: SIGNED32;
	BEGIN
		buf[0] := CHR(x DIV 100H MOD 100H);
		buf[1] := CHR(x MOD 100H);
		conn.Send( buf, 0, 2, TRUE, r )
	END WriteInt;

	PROCEDURE WriteLInt* (conn: Connection; x: SIGNED32);
	VAR buf: ARRAY 4 OF CHAR; r: SIGNED32;
	BEGIN
		buf[0] := CHR(x DIV 1000000H MOD 100H);
		buf[1] := CHR(x DIV 10000H MOD 100H);
		buf[2] := CHR(x DIV 100H MOD 100H);
		buf[3] := CHR(x MOD 100H);
		conn.Send( buf, 0, 4, TRUE, r )
	END WriteLInt;

	PROCEDURE WriteString* (conn: Connection; CONST s: ARRAY OF CHAR);
	VAR
		cs: ARRAY 2 OF CHAR;
		i, r: SIGNED32;
	BEGIN  i := 0;
		WHILE s[i] # 0X DO  INC( i )  END;
		conn.Send( s, 0, i, FALSE, r);
		cs[0] := CR; cs[1] := LF;
		conn.Send( cs, 0, 2, TRUE, r )
	END WriteString;

(******************************** UDP **************************************)

	PROCEDURE OpenSocket* ( VAR soc: Socket; locPort: UNSIGNED16; VAR res: SIGNED16 );
	VAR r: SIGNED32;
	BEGIN
		NEW( soc, locPort MOD 10000H, r );
		IF r = UDP.Ok THEN res := done  ELSE  res := error  END
	END OpenSocket;

	PROCEDURE CloseSocket* (S: Socket);
	BEGIN
		S.Close();
	END CloseSocket;

	PROCEDURE AvailableDG* (soc: Socket): SIGNED32;
	BEGIN
		RETURN 0
	END AvailableDG;

	PROCEDURE SendDG* ( soc: Socket; remIP: IPAdr; remport: UNSIGNED16; pos, len: SIGNED32; CONST buf: ARRAY OF S.BYTE );
	VAR res: INTEGER;
	BEGIN
		soc.Send( remIP, remport MOD 10000H, S.VAL( Bytes, buf ), pos, len, res )
	END SendDG;

	PROCEDURE ReceiveDG* ( soc: Socket; VAR remIP: IPAdr; VAR remport: UNSIGNED16; 
							pos: SIGNED32; VAR len: SIGNED32; VAR buf: ARRAY OF S.BYTE );
	VAR res: SIGNED32; port: SIGNED32; length: SIZE;
	BEGIN
		soc.Receive( S.VAL( Bytes, buf ), pos, len, 0, remIP, port, length, res );
		len := length(SIGNED32);
		remport := SHORT( port) 
	END ReceiveDG;

	(** Write 2 bytes in network byte ordering to buf[pos]. *)

	PROCEDURE PutInt* (VAR buf: ARRAY OF S.BYTE; pos: SIGNED16; x: SIGNED16);
	BEGIN
		buf[pos] := CHR(x DIV 100H MOD 100H);
		buf[pos+1] := CHR(x MOD 100H)
	END PutInt;

	(** Write 4 bytes in network byte ordering to buf[pos]. *)

	PROCEDURE PutLInt* (VAR buf: ARRAY OF S.BYTE; pos: SIGNED16; x: SIGNED32);
	BEGIN
		buf[pos] := CHR(x DIV 1000000H MOD 100H);
		buf[pos+1] := CHR(x DIV 10000H MOD 100H);
		buf[pos+2] := CHR(x DIV 100H MOD 100H);
		buf[pos+3] := CHR(x MOD 100H)
	END PutLInt;

	(** Read 2 bytes in network byte ordering from buf[pos]. *)

	PROCEDURE GetInt* (CONST buf: ARRAY OF S.BYTE; pos: SIGNED16; VAR x: SIGNED16);
	BEGIN
		x := ORD(buf[pos])*100H + 
			ORD(buf[pos+1])
	END GetInt;

	(** Read 4 bytes in network byte ordering from buf[pos]. *)

	PROCEDURE GetLInt* (CONST buf: ARRAY OF S.BYTE; pos: SIGNED16; VAR x: SIGNED32);
	BEGIN
		x := ORD( buf[pos] )*1000000H + 
			ORD( buf[pos+1] )*10000H + 
			ORD( buf[pos+2] )*100H + 
			ORD( buf[pos+3] )
	END GetLInt;
	
	PROCEDURE WriteAccess (service, user, password, host: ARRAY OF CHAR);
	BEGIN
		Texts.WriteString(W, service);
		Texts.Write(W, ":"); Texts.WriteString(W, user);
		Texts.Write(W, ":"); Texts.WriteString(W, password);
		Texts.Write(W, "@"); Texts.WriteString(W, host); Texts.WriteLn(W) (* ;
		Texts.Append(Oberon.Log, W.buf) *)
	END WriteAccess;

	PROCEDURE Show*;
	VAR p: Access;
	BEGIN
		p := accessList;
		Texts.WriteLn(W); Texts.WriteString(W, "NetSystem.Show"); Texts.WriteLn(W);
		WHILE p # NIL DO
			WriteAccess(p.service, p.user, "hidden password", p.host);
			p := p.next
		END;
		Texts.Append(Oberon.Log, W.buf)
	END Show;

BEGIN
	verbose := FALSE;
	anyIP := IP.NilAdr;
	allIP := IP.NilAdr;
	allIP.usedProtocol := IP.IPv4;
	allIP.ipv4Adr := SIGNED32( 0FFFFFFFFH );

	Texts.OpenWriter(W);

	accessList := NIL;
	Start
END NetSystem.