--=============================================== -- -- The Door Lock -- --=============================================== ----------------------------------------------- -- Useful operators -- HasHappened(X) is true iff X has been true at some point node HasHappened(X : bool) returns (Y : bool); let Y = X or (false -> pre Y); tel -- Since(X,Y) is true precisely when X has been true at some point, -- and Y has been continuously true afterwards node Since( X, Y : bool ) returns ( SinceXY : bool ); let SinceXY = X or (Y and (false -> pre SinceXY)); tel ----------------------------------------------- ----------- -- KeyPad ----------- -- This implementation assumes that Digit is in the range [0..9] -- No measures are taken to assure that nothing bad happens -- if Digit is out of range. -- The keypad remembers a digit in an incomplete code only up to a certain time. -- After that it forgets them. This is to deal to the situation in which -- in which a user enters an incomplete code and then leaves. -- Specification contract KeypadSpec( Digit: int; Press: bool ) returns ( Request: bool; Code: int ); let var PreRequest: bool = false -> pre Request; assume "A1" 0 <= Digit and Digit <= 9; -- R0: Whenever there is a request the output code is in the range [0..999] guarantee "R0" Request => (0 <= Code and Code <= 999); -- R1: The component will not send an unlock requests before its third cycle guarantee "R1" (not Request) -> (not PreRequest) -> true; -- R2: Any two distinct unlock requests are separated by at least 2 clock cycles guarantee "R2"true -> Request => not PreRequest and not pre PreRequest; -- R3: Code is always non-negative guarantee "R3" Code >= 0; -- R4: Whenever there is a request a digit is pressed guarantee "R4" Request => Press; tel -- Implementation node Keypad( Digit: int; Press: bool ) returns ( Request: bool; Code: int ); (*@contract import KeypadSpec(Digit, Press) returns (Request, Code); *) var PressedDigits, ElapsedTime, ExpirationTime: int; IncompleteCode: bool; let -- Maximum number of cycles an incomplete code is kept ExpirationTime = 9; -- True iff only one or two digits in a group of 3 have been entered so far IncompleteCode = false -> 0 < pre PressedDigits and pre PressedDigits < 3; -- ElapsedTime counts the number of cycles since the last digit -- in a group of 2 was pressed, up to a maximum number of cycles. -- When the maximum is reached it is resets to 0 ElapsedTime = 0 -> if Press then 0 else if pre ElapsedTime < ExpirationTime and IncompleteCode then pre ElapsedTime + 1 else 0; -- PressedDigits counts the number of pressed digits, resetting every 3 of them -- or when too much time has passed since the last pressed digit in a group -- of two. PressedDigits = (if Press then 1 else 0) -> if ElapsedTime = ExpirationTime then 0 else if not Press then pre PressedDigits else if pre PressedDigits = 3 then 1 else pre PressedDigits + 1; -- Request is true every 3 digit presses Request = false -> Press and PressedDigits = 3; -- Every sequence D1, D2, D3 of 3 pressed digits is converted -- into the number (100*D1 + 10*D2 + D3) Code = if Press then (Digit -> if PressedDigits = 1 then Digit else 10 * (pre Code) + Digit) else (0 -> pre Code); -- 0 arbitrary value tel ------------ -- Control ------------ -- Specification contract ControlSpec( Request : bool; Code : int; MasterKey : bool ) returns ( CurrentCode : int; Granted : bool ); let -- R1 does not hold without the assumption that Code is always non-negative assume "A1" 0 <= Code ; assume "A2" Request => Code <= 999; -- R1: Until the access code is first set, the door cannot be unlocked. guarantee "R1" not HasHappened(Request and MasterKey) => not Granted; -- R2: Unless the current code is being changed, an unlock request is granted -- whenever the provided code equals the current access code. guarantee "R2" (Request and (Code = CurrentCode) and not MasterKey) => Granted; -- R3: An unlock request is granted only if the provided code equals the current access code. guarantee "R3" Granted => Code = CurrentCode; -- R4: Once it has been set, the current code can change only when the master key is inserted. guarantee "R4" true -> (HasHappened(MasterKey and Request) and CurrentCode <> pre CurrentCode => MasterKey); -- The unlocking of the door is granted only if there is a request -- and the master key is not inserted. guarantee "R5" Granted => Request and not MasterKey; guarantee "R6" -1 <= CurrentCode and CurrentCode <= 999; tel -- Implementation node Control( Request : bool; Code : int; MasterKey : bool ) returns ( CurrentCode : int; Granted : bool ); (*@contract import ControlSpec(Request, Code, MasterKey) returns (CurrentCode, Granted); *) let -- CurrentCode is an illegal value (-1) until it is explicitly set -- This is to prevent accidently unlockings before that CurrentCode = -1 -> if MasterKey and Request then Code else pre CurrentCode; Granted = Request and (Code = CurrentCode) and not MasterKey; -- Note the addition of (not MasterKey) in the definition of Granted, -- to prevent unlocking while an internal code is being entered tel --------------------------------------------------------------------- -- Lock node --------------------------------------------------------------------- -- The implementation below ignores any Unlock request from the controller -- while the door is being kept unlocked because of a previous request. -- This prevents the possibility of keeping the door unlocked -- indefinitely by continually entering the code -- Specification contract LockSpec( Digit: int; Press: bool; MasterKey: bool ) returns ( Unlocking : bool ); let -- C counts the number of cycles Unlocking has be continuously true so far var C: int = if Unlocking then (1 -> pre C + 1) else 0; var Locks: bool = false -> pre Unlocking and not Unlocking; assume "A1" 0 <= Digit and Digit <= 9; -- R1: The door will never be held open for more than 4 clock cycles at a time. guarantee "R1" (C <= 4); -- R2: Once a door locks (after being unlocked) it remains locked -- as long as no one uses the keypad guarantee "R2" Since(Locks, not Press) => not Unlocking; tel -- Implementation node Lock( Digit: int; Press: bool; MasterKey: bool ) returns ( Unlocking : bool ); (*@contract import LockSpec(Digit, Press, MasterKey) returns (Unlocking); *) var CurrentCode, Code, Timer: int; Request, Granted: bool; let (Request, Code) = Keypad(Digit, Press); (CurrentCode, Granted) = Control(Request, Code, MasterKey); -- A timer is set to 4 if Granted holds *and* the door was previously -- locked. It decreases by 1 while the door is kept unlocked. -- Otherwise it stays equal to zero. Timer = 0 -> if pre Unlocking then pre Timer - 1 else if Granted then 4 else 0; -- The door is kept unlocked while the timer is positive Unlocking = Timer > 0; tel