MODULE Bluetooth;	(** AUTHOR "be"; PURPOSE "Core Bluetooth types/functions"; *)

IMPORT
	Objects, Streams;

(**---- general Bluetooth types ----*)

CONST
	(** Result codes. res > 0: command specific error *)
	Ok* = 0;
	ErrTimeout* = -1;
	ErrInvalidPacket* = -2;
	ErrInvalidEvent* = -3;
	ErrInvalidParameters* = -4;
	ErrSendError* = -5;

	BDAddrLen* = 6;	(** length of Bluetooth device address *)
	DeviceClassLen* = 3;	(** length of Bluetooth class of device *)

TYPE
	BDAddr* = ARRAY BDAddrLen OF CHAR;	(** Bluetooth device address *)
	DeviceClass* = ARRAY DeviceClassLen OF CHAR;	(** Bluetooth class of device *)

(**---- HCI packet queue ----*)
CONST
	(** queue types *)
	Default* = 0;	(** default queue *)
	Command* = 1;	(** command queue *)
	ACL* = 2;	(** ACL data packet queue *)
	SCO* = 3;	(** SCO data packet queue *)
	Event* = 4;	(** HCI event queue *)
	Error* = 5;	(** error queue *)
	Negotiation* = 6;	(** negotiation queue *)
	NumQueues = 7;

	MaxACLDataLen* = 256;
	MaxSCODataLen* = 256;
	MaxEventParamLen* = 256;
	MaxUnknownDataLen* = 256;
	MaxLen* = 256;

TYPE
	Packet* = POINTER TO RECORD	(** generic packet type *)
		next: Packet
	END;

	ACLPacket* = POINTER TO RECORD(Packet)	(** ACL packet, see specs chapter 4.4.3 *)
		handle*,	(** connection handle *)
		PB*, 	(** packet boundary flag *)
		BC*, 	(** broadcast flag *)
		len*: LONGINT;	(** length of data, in bytes *)
		data*: ARRAY MaxACLDataLen OF CHAR	(** data *)
	END;

	SCOPacket* = POINTER TO RECORD(Packet)	(** SCO packet, see specs chapter 4.4.3 *)
		handle*,	(** connection handle *)
		len*: LONGINT;	(** length of data, in bytes *)
		data*: ARRAY MaxSCODataLen OF CHAR	(** data *)
	END;

	EventPacket* = POINTER TO RECORD(Packet)	(** HCI event packet, see specs chapter 4.4.2 *)
		code*: CHAR;	(** event code *)
		paramLen*: LONGINT;	(** parameter length, in bytes *)
		params*: ARRAY MaxEventParamLen OF CHAR	(** parameter values *)
	END;

	UnknownPacket* = POINTER TO RECORD(Packet) (** unknown packet...should not happen ;-) *)
		len*: LONGINT;	(** length of data, in bytes *)
		data*: ARRAY MaxUnknownDataLen OF CHAR	(** data *)
	END;

	(** packet filter/notifier: the filter is called first and should return quickly. If it returns TRUE the
		correspoding notifier will be called *)
	PacketFilter* = PROCEDURE{DELEGATE} (packet: Packet): BOOLEAN;
	PacketNotify* = PROCEDURE{DELEGATE} (packet: Packet);

	Filter = POINTER TO RECORD
		filter: PacketFilter;
		notify: PacketNotify;
		next: Filter
	END;

	(** used if we need to know which timer has expired *)
	IDTimer* = OBJECT
		VAR
			t: Objects.Timer;
			handler: IDTimerHandler;

		PROCEDURE &Init*(handler: IDTimerHandler; timeout: LONGINT);
		BEGIN
			SELF.handler := handler; NEW(t);
			Objects.SetTimeout(t, TimeoutHandler, timeout)
		END Init;

		PROCEDURE Cancel*;
		BEGIN {EXCLUSIVE} Objects.CancelTimeout(t)
		END Cancel;

		PROCEDURE TimeoutHandler;
		BEGIN {EXCLUSIVE} handler(SELF)
		END TimeoutHandler;
	END IDTimer;

	IDTimerHandler* = PROCEDURE {DELEGATE} (sender: IDTimer);

	(** packet queue *)
	Queue* = OBJECT
		VAR
			head, tail: Packet;
			filters: Filter;
			dead: BOOLEAN;
			expired: IDTimer;
			getNext: Packet;
			inGetNext: LONGINT;

		PROCEDURE &Init*;
		BEGIN
			inGetNext := 0; dead := FALSE;
			NEW(filters)	(* dummy head *)
		END Init;

		(** closes a queue and aborts any pending 'Get' requests *)
		PROCEDURE Close*;
		BEGIN {EXCLUSIVE}
			dead := TRUE
		END Close;

		(** clears the queue *)
		PROCEDURE Clear*;
		BEGIN {EXCLUSIVE}
			head := NIL; tail := NIL
		END Clear;

		(** add a packet to the queue *)
		PROCEDURE Add*(packet: Packet);
		BEGIN
			IF ~CheckPacketFilters(packet) THEN	(* packet filters are priorized *)
				BEGIN {EXCLUSIVE}
					IF (tail # NIL) THEN tail.next := packet; tail := packet
					ELSE head := packet; tail := packet
					END
				END
			END
		END Add;

		PROCEDURE HandleTimeout(sender: IDTimer);
		BEGIN {EXCLUSIVE} expired := sender
		END HandleTimeout;

		(** blocks until a HCI packet is available or a timeout occurs. Packet filters are priorized over the Get request *)
		PROCEDURE Get*(VAR p: Packet; timeout: LONGINT; VAR res: LONGINT);
		VAR timer: IDTimer;
		BEGIN {EXCLUSIVE}
			IF (head = NIL) THEN
				NEW(timer, HandleTimeout, timeout);
				AWAIT((head # NIL) OR (expired = timer) OR dead);
				IF (expired # timer) THEN timer.Cancel END
			END;
			IF (head # NIL) THEN
				p := head; head := head.next;
				IF (head = NIL) THEN tail := NIL END;
				p.next := NIL; res := 0
			ELSE
				p := NIL; res := ErrTimeout
			END
		END Get;

		(** blocks until the next HCI packet is available or a timeout occurs. Packet filters are priorized over the
			GetNext request.
		*)
		PROCEDURE GetNextFilter(p: Packet): BOOLEAN;
		BEGIN
			RETURN TRUE
		END GetNextFilter;

		PROCEDURE GetNextHandler(p: Packet);
		BEGIN
			getNext := p
		END GetNextHandler;

		(* naaa...won't work. besser: filter rein, der alles frisst, dann wieder rausnehmen *)
		PROCEDURE GetNext*(VAR p: Packet; timeout: LONGINT; VAR res: LONGINT);
		VAR f: Filter; timer: IDTimer;
		BEGIN {EXCLUSIVE}
			(* lock *)
			AWAIT(inGetNext = 0); INC(inGetNext);

			getNext := NIL;

			(* plug-in greedy filter *)
			NEW(f); f.filter := GetNextFilter; f.notify := GetNextHandler;
			f.next := filters.next; filters.next := f;

			NEW(timer, HandleTimeout, timeout);
			AWAIT((getNext # NIL) OR (expired = timer) OR dead);

			(* remove greedy filter *)
			filters.next := f.next;

			IF (getNext # NIL) THEN p := getNext; res := 0
			ELSE p := NIL; res := ErrTimeout
			END;

			(* unlock *)
			DEC(inGetNext)
		END GetNext;

		(** registers a packet filter/handler. Multiple filters/handlers may be registered *)
		PROCEDURE RegisterPacketFilter*(filter: PacketFilter; notify: PacketNotify);
		VAR f: Filter;
		BEGIN {EXCLUSIVE}
			NEW(f); f.filter := filter; f.notify := notify;
			f.next := filters.next; filters.next := f
		END RegisterPacketFilter;

		(** removes a registered filter/handler. *)
		PROCEDURE UnregisterPacketFilter*(notify: PacketNotify);
		VAR p,q: Filter;
		BEGIN {EXCLUSIVE}
			q := filters.next; p := filters;
			WHILE (q # NIL) DO
				IF (q.notify = notify) THEN
					p.next := q.next
				END;
				q := q.next
			END
		END UnregisterPacketFilter;

		(* checks if a packet filter/handler wants to handle the packet *)
		PROCEDURE CheckPacketFilters(packet: Packet): BOOLEAN;
		VAR f: Filter; notify: PacketNotify; res: BOOLEAN;
		BEGIN
			res := FALSE;
			BEGIN {EXCLUSIVE}
				notify := NIL;
				f := filters.next;
				WHILE (f # NIL) DO
					IF f.filter(packet) THEN res := TRUE; notify := f.notify; f := NIL
					ELSE f := f.next
					END
				END
			END;
			IF (notify # NIL) THEN notify(packet) END;
			RETURN res
		END CheckPacketFilters;
	END Queue;

	(**---- abstract transport layer ----*)
	TransportLayer* = OBJECT
		VAR
			name-: ARRAY 32 OF CHAR;
			out*: Streams.Writer;
			in*: Streams.Reader;
			sink-: ARRAY NumQueues OF Queue;

		PROCEDURE &Init*(name: ARRAY OF CHAR; sender: Streams.Sender; receiver: Streams.Receiver);
		VAR q: Queue;
		BEGIN
			COPY(name, SELF.name);
			NEW(q); sink[Default] := q	(* install default queue *)
		END Init;

		(** close the transport layer *)
		PROCEDURE Close*;
		END Close;

		(** install a queue for certain HCI packet types *)
		PROCEDURE SetSink*(type: LONGINT; queue: Queue);
		BEGIN {EXCLUSIVE}
			sink[type] := queue
		END SetSink;

		(** get the queue for certain HCI packet types *)
		PROCEDURE GetSink*(type: LONGINT): Queue;
		BEGIN {EXCLUSIVE}
			RETURN sink[type]
		END GetSink;

		(** send a HCI packet *)
		PROCEDURE Send*(type: LONGINT; VAR data: ARRAY OF CHAR; ofs, len: LONGINT; VAR res: LONGINT);
		BEGIN
			HALT(301)
		END Send;

		PROCEDURE Send1H*(type: LONGINT; VAR hdr: ARRAY OF CHAR; hdrlen: LONGINT; VAR data: ARRAY OF CHAR; ofs, len: LONGINT; VAR res: LONGINT);
		BEGIN
			HALT(301)
		END Send1H;

		PROCEDURE Send2H*(type: LONGINT; VAR hdr1: ARRAY OF CHAR; hdr1len: LONGINT;
								VAR hdr2: ARRAY OF CHAR; hdr2len: LONGINT;
								VAR data: ARRAY OF CHAR; ofs, len: LONGINT; VAR res: LONGINT);
		BEGIN
			HALT(301)
		END Send2H;
	END TransportLayer;

(** transforms 'character string' into an array of char.
	string = char { " " char } 0X.
	char = hexdigit hexdigit.
	hexdigit = "0"|..|"9"|"A"|..|"F".
*)
PROCEDURE StringToParam*(string: ARRAY OF CHAR; VAR param: ARRAY OF CHAR; VAR len: LONGINT);
VAR i, h, l: LONGINT; error: BOOLEAN;

	PROCEDURE Value(c: CHAR): LONGINT;
	BEGIN
		IF ("0" <= c) & (c <= "9") THEN RETURN ORD(c)-ORD("0")
		ELSE
			c := CAP(c);
			IF ("A" <= c) & (c <= "F") THEN RETURN ORD(c)-ORD("A")+10 END
		END;
		RETURN -1
	END Value;

BEGIN
	i := 0; len := 0; error := FALSE;
	WHILE ~error & (string[i] # 0X) DO
		h := Value(string[i]); l := Value(string[i+1]);
		IF (h # -1) & (l # -1) THEN
			param[len] := CHR(h*10H+l); INC(len);
			INC(i, 2);
			IF (string[i] # 0X) THEN
				IF (string[i] = " ") THEN INC(i)
				ELSE error := TRUE; len := 0
				END
			END
		ELSE error := TRUE; len := 0
		END
	END;
	param[len] := 0X
END StringToParam;


PROCEDURE CharArrayToString*(buf: ARRAY OF CHAR; ofs, len: LONGINT; VAR string: ARRAY OF CHAR);
VAR i, pos, maxLen: LONGINT; c: CHAR;

	PROCEDURE Char(v: LONGINT): CHAR;
	BEGIN
		ASSERT((0 <= v) & (v < 10H));
		IF (v < 10) THEN RETURN CHR(ORD("0") + v)
		ELSE RETURN CHR(ORD("A") + v - 10)
		END
	END Char;

BEGIN
	i := 0; pos := 0; maxLen := LEN(string)-1-3;
	WHILE (i < len) & (pos < maxLen) DO
		c := buf[ofs+i];
		string[pos] := Char(ORD(c) DIV 10H); INC(pos);
		string[pos] := Char(ORD(c) MOD 10H); INC(pos);
		string[pos] := " "; INC(pos);
		INC(i)
	END;
	string[pos] := 0X
END CharArrayToString;


END Bluetooth.