models.examples.case_studies.firewire.als Maven / Gradle / Ivy
module examples/case_studies/firewire
/*
* A model of leader election in the Firewire protocol.
*
* Adapted from:
* [DG+01] M.C.A. Devillers, W.O.D. GriAEoen, J.M.T Romijn, and F.W. Vaandrager.
* Verification of a leader election protocol -- formal methods applied to IEEE
* 1394. Technical Report CSI-R9728, Computing Science Institute, University of
* Nijmegen, December 1997. Also, Formal Methods in System Design, 2001.
*
* This model describes a leader election protocol used in Firewire, an IEEE
* standard for connecting consumer electronic devices. The model is a
* straightforward translation into Alloy of a model [DG+01] developed in Lynch's
* IO Automata which has been analyzed using PVS, a theorem prover, but which, as
* far as we know, has not been subjected to fully automatic analysis. We are able
* to express the key correctness property -- that exactly one leader is elected
* -- more directly, as a trace property rather than a refinement property, and
* can check it without the need for the 15 invariants used in the more
* traditional proof. And the analysis does not hardwire
* a particular topology, so would be tricky to do with a standard model checker.
*
* The network is assumed to consist of a collection of nodes connected by
* links. Each link between a pair of nodes is matched by a link in the other
* direction. Viewing a link and its dual as a single, undirected edge, the
* network as a whole is assumed to form a tree. The purpose of the algorithm is
* to construct such a tree; in the model, this is achieved by labelling some
* subset of the links as parent links (each pointing from a node to its parent),
* and by marking a single node as the root.
*
* The algorithm, described in detail elsewhere [DG+01], works briefly as
* follows. When a node detects that all of its incoming links (or all but one)
* has been marked as a parent link, it sends a message on each outgoing link,
* either an acknowledgment (indicating its willingness to act as parent), or a
* request (indicating its desire to be a child), according to whether the dual of
* the outgoing link has been marked or not. Leaf nodes (with only one incoming
* link) may thus initiate the algorithm by sending requests to their adjacent
* nodes. Performing this action changes a node's status from {\em waiting} to
* {\em active}. A node that is still waiting, and which receives a message on a
* link, may label that link a parent link. Once active, a node that receives an
* acknowledgment on a link may also label the link, but if it receives a request,
* instead changes its node status to {\em contending}. The resolving of
* contentions is modelled simplistically by a single action that arbitrarily
* labels one of the two links a pair of contending nodes. Finally, a node all of
* whose incoming links are parent links designates itself a root.
*
* The specification is given below. Each signature introduces a basic type
* and some relations whose first column has that type:
*
* \begin{itemize}
*
* \item {\em Msg} represents the type of messages. {\em Req} is the request
* message and {\em Ack} is the acknowledgment message; these are actually
* declared as singleton (keyword {\em static}) subsets of {\em Msg}, the set of
* all messages, that form a partition (keyword {\em part}).
*
* \item {\em Node} represents the nodes of the network. The relations {\em to}
* and {\em from} associate each node with a set of incoming and outgoing links
* respectively.
*
* \item {\em Link} represents the links. The relations {\em target} and {\em
* source} map a link to its end nodes; {\em reverse} maps a link to its dual. The
* facts in the signatures {\em Node} and {\em Link} ensure that all these
* relations are consistent with one another: that the incoming links of a node
* are those whose target is that node, etc.
*
* \item {\em Op} introduces a set of names for the operations of the
* protocol. This is merely for convenience; it allows us to ask for an execution
* in which named operations occur, or example.
*
* \item {\em State} represents the global states. Each state has a partition of
* the nodes into one of four statuses: {\em waiting} to participate, {\em active}
* (having sent messages on outgoing links), {\em contending} (having sent a
* request on a link and received a request on its dual), and {\em elected}
* (having designated itself as a root). A set of links are labelled as parent
* links. There is a message queue associated with each link. Finally, the state
* is associated with the operation that produced it.
*
* \item {\em Queue} represents the message queues. Each queue has a slot that
* optionally contains a message; the relation {\em slot} is a partial function
* from queues to messages. In our first attempt at a model, we represented a
* queue as a sequence (a partial function from a prefix of the integers to
* messages), but having checked that no queue ever contained more than one
* message, we simplified the model. The {\em overflow} field is included just in
* case this was a mistake; a write to a queue that already contains a message
* puts an arbitrary value there, which is easily detected.
*
* \end{itemize}
*
* The {\em facts} record the assumptions about the topology. The one named {\em
* Topology} says that there is some partial function on nodes and some root such
* that (1) every node is reachable from the root ({\tt *r} being the reflexive
* transitive closure of the relation {\tt r}); (2) there are no cycles (expressed
* by saying that the transitive closure has no intersection with the identity
* relation on nodes); and (3) the relation obtained by following the {\em source}
* relation backwards (from a node to the link for which it is a source), and then
* the {\em target} relation forwards (from the link to its target) is this
* relation, plus its transpose (so that each tree edge becomes two
* links). Although the quantifier appears to be higher-order, it will be
* skolemized away by the analyzer.
*
* The {\em functions} of the model are parameterized formulas. The function {\em
* Trans} relates a pre-state {\tt s} to a post-state {\tt s'}. It has a case for
* each operation. Look at the clause for the operation {\em WriteReqOrAck}, for
* example. If this operation is deemed to have occurred, each of the constraints
* in the curly braces must hold. The first says that the labelling of links as
* parent links is unchanged. The second constraint (the quantified formula)
* constrains with respect to the node at which the operation occurs. The
* subformulas, from first to last, say that the node belongs to the waiting set
* before and the active set afterwards; that there is at most one ({\em sole})
* link that is incoming but not a parent link in the pre-state; that there are no
* changes to node status except at this node; that a message is queued onto each
* outgoing link; and that queues on all other links are unchanged.
*
* An 'invoked' function is simply short for the formula in its body with the
* formal arguments replaced by the actual expressions. {\em WriteQueue}, for
* example, says that if the queue's slot is not filled in the pre-state, then the
* new queue in the post-state (given the local name {\tt q}) contains the message
* {\tt m} in its slot, and has no message in its overflow. Otherwise, some
* message is placed arbitrarily in the overflow, and the slot is
* unconstrained. In {\em WriteReqOrAck}, the arguments {\tt s} and {\tt s'} are
* bound to the {\tt s} and {\tt s'} of {\em Trans}; {\tt x} is bound to one of
* the outgoing links from the set {\tt n.from}; and {\tt msg} is bound either to
* the acknowledgment or request message.
*
* The function {\em Execution} constrains the set of states. It makes use of a
* library module that defines a polymorphic ordering relation. The expression
* {\tt Ord[State]} gives an ordering on all states. The two formulas of the
* function say that {\tt Initialization} holds in the first state, and that any
* pair of adjacent states is related by {\tt Trans}. The function {\em NoRepeats}
* adds the constraints that there are no equivalent states in the trace, and that
* no stuttering occurs.
*
* The three assertions are theorems for which the analyzer will search for
* counterexamples. They assert respectively that: in every state of the trace,
* there is at most one node that has been elected; that there is some state in
* which a node has been elected; and that no queue overflows.
*
* The rest of the model is a collection of commands executed to find instances of
* the functions or counterexamples to the theorems. We started by presenting a
* variety of functions as a sanity check; here, only one is given, that asks for
* an execution involving 2 nodes, 4 links, 4 queues and a trace of 6 states. The
* standard semantics of these {\em scope} declarations in Alloy is that the
* numbers represent an upper bound, so an instance may involve fewer than 4
* queues, for example. The ordering module (not shown here), however, for
* technical reasons, constrains the ordered set to match its scope, so a trace
* with fewer than 6 states will not be acceptable.
*
* We then established some bounds on the diameter of the state machine for
* various topology bounds. For 2 nodes and 2 links, for example, there are no
* non-repeating traces of length 4; checking traces of length 3 is thus
* sufficient in this case. The number of queues was limited to 5, to accommodate
* the empty queue, a queue containing an {\tt Ack} or {\tt Req}, and each of
* these with overflow. For 3 nodes and 6 links, a trace length of 8 suffices.
*
* We then checked that for these various topology bounds, the queues never
* overflow. Finally, we checked the correctness properties, taken advantage of
* the earlier results that justify the short traces and queues. We are thus able
* to verify the properties for all topologies involving the given number of nodes
* and links, without any assumptions about trace length, queue size or the
* particular topological structure.
*
* author: Daniel Jackson
* visualization: Robert Seater
*/
open util/ordering[State] as ord
abstract sig Msg {}
one sig Req, Ack extends Msg {}
sig Node {to, from: set Link} {
to = {x: Link | x.target = this}
from = {x: Link | x.source = this}
}
sig Link {target, source: Node, reverse: Link} {
reverse.@source = target
reverse.@target = source
}
/**
* at most one link between a pair of nodes in a given direction
*/
fact {no x,y: Link | x!=y && x.source = y.source && x.target = y.target}
/**
* topology is tree-like: acyclic when viewed as an undirected graph
*/
fact Topology {
some tree: Node lone -> Node, root: Node {
Node in root.*tree
no ^tree & iden & Node->Node
tree + ~tree = ~source.target
}
}
sig Op {}
one sig Init, AssignParent, ReadReqOrAck, Elect, WriteReqOrAck,
ResolveContention, Stutter extends Op {}
sig State {
disj waiting, active, contending, elected: set Node,
parentLinks: set Link,
queue: Link -> one Queue,
op: Op -- the operation that produced the state
} {
waiting + active + contending + elected = Node
}
pred SameState [s, s': State] {
s.waiting = s'.waiting
s.active = s'.active
s.contending = s'.contending
s.elected = s'.elected
s.parentLinks = s'.parentLinks
all x: Link | SameQueue [s.queue[x], s'.queue[x]]
}
pred Trans [s, s': State] {
s'.op != Init
s'.op = Stutter => SameState [s, s']
s'.op = AssignParent => {
some x: Link {
x.target in s.waiting & s'.waiting
NoChangeExceptAt [s, s', x.target]
! IsEmptyQueue [s, x]
s'.parentLinks = s.parentLinks + x
ReadQueue [s, s', x]
}}
s'.op = ReadReqOrAck => {
s'.parentLinks = s.parentLinks
some x: Link {
x.target in s.(active + contending) & (PeekQueue [s, x, Ack] => s'.contending else s'.active)
NoChangeExceptAt [s, s', x.target]
! IsEmptyQueue [s, x]
ReadQueue [s', s, x]
}}
s'.op = Elect => {
s'.parentLinks = s.parentLinks
some n: Node {
n in s.active & s'.elected
NoChangeExceptAt [s, s', n]
n.to in s.parentLinks
QueuesUnchanged [s, s', Link]
}}
s'.op = WriteReqOrAck => {
-- note how this requires access to child ptr
s'.parentLinks = s.parentLinks
some n: Node {
n in s.waiting & s'.active
lone n.to - s.parentLinks
NoChangeExceptAt [s, s', n]
all x: n.from |
let msg = (x.reverse in s.parentLinks => Ack else Req) |
WriteQueue [s, s', x, msg]
QueuesUnchanged [s, s', Link - n.from]
}}
s'.op = ResolveContention => {
some x: Link {
let contenders = x.(source + target) {
contenders in s.contending & s'.active
NoChangeExceptAt [s, s', contenders]
}
s'.parentLinks = s.parentLinks + x
}
QueuesUnchanged [s, s', Link]
}
}
pred NoChangeExceptAt [s, s': State, nodes: set Node] {
let ns = Node - nodes {
ns & s.waiting = ns & s'.waiting
ns & s.active = ns & s'.active
ns & s.contending = ns & s'.contending
ns & s.elected = ns & s'.elected
}}
sig Queue {slot: lone Msg, overflow: lone Msg}
pred SameQueue [q, q': Queue] {
q.slot = q'.slot && q.overflow = q'.overflow
}
pred ReadQueue [s, s': State, x: Link] {
-- let q = s'.queue[x] | no q.(slot + overflow)
no s'.queue[x].(slot + overflow)
all x': Link - x | s'.queue[x'] = s.queue[x']
}
pred PeekQueue [s: State, x: Link, m: Msg] {
m = s.queue[x].slot
}
pred WriteQueue [s, s': State, x: Link, m: Msg] {
let q = s'.queue[x] |
no s.queue[x].slot =>
( q.slot = m && no q.overflow) else
some q.overflow
}
pred QueuesUnchanged [s, s': State, xs: set Link] {
all x: xs | s'.queue[x] = s.queue[x]
}
pred IsEmptyQueue [s: State, x: Link] {
no s.queue[x].(slot + overflow)
-- let q = s.queue[x] | no q.(slot + overflow)
}
pred Initialization [s: State] {
s.op = Init
Node in s.waiting
no s.parentLinks
all x: Link | IsEmptyQueue [s, x]
}
pred Execution {
Initialization [ord/first]
all s: State - ord/last | let s' = ord/next[s] | Trans [s, s']
}
pred ElectionHappens {
Execution
some s: State | some s.elected
some s: State | no s.elected
}
pred NoRepeats {
Execution
no s, s': State | s!=s' && SameState [s, s']
no s: State | s.op = Stutter
}
pred NoShortCuts {
all s: State | -- remove this to speed up analysis - Ord[State].last - OrdPrev (Ord[State].last) |
! Trans [s, ord/next[ord/next[s]]]
}
assert AtMostOneElected {
Execution => (all s: State | lone s.elected)
}
assert OneEventuallyElected {
Execution => (some s: State | some s.elected)
}
assert NoOverflow {
Execution => (all s: State, x: Link | no s.queue[x].overflow)
}
run Execution for 7 Op, 2 Msg,
2 Node, 4 Link, 4 Queue, 6 State expect 1
run ElectionHappens for 7 Op, 2 Msg,
exactly 3 Node, 6 Link, 3 Queue, 7 State expect 1
-- solution for 3 State but not for 4 State
run NoRepeats for 7 Op, 2 Msg,
2 Node, 2 Link, 2 Queue, 4 State expect 0
-- solution for 8 but not 9 State
run NoRepeats for 7 Op, 2 Msg,
3 Node, 6 Link, 6 Queue, 8 State expect 0
-- only 5 queues needed: just count
-- no solution: establishes at most 3 queues needed
check NoOverflow for 7 Op, 2 Msg,
3 Node, 6 Link, 5 Queue, 9 State expect 0
check AtMostOneElected for 7 Op, 2 Msg,
3 Node, 6 Link, 3 Queue, 9 State expect 0
check OneEventuallyElected for 7 Op, 2 Msg,
3 Node, 6 Link, 3 Queue, 9 State expect 1
// DEFINED VARIABLES
// Defined variables are uncalled, no-argument functions.
// They are helpful for getting good visualization.
fun queued: State -> Link -> Msg {
{s: State, L: Link, m: Msg | m in L.(s.queue).slot}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy