/**
	Needham-Schroeder message passing protocol (patched). 
	|	msg1:	agentA -> agentB	(keyB, agentA, nonceA, 0)
	|	msg2:	agentB -> agentA	(keyA, agentB, nonceA, nonceB)
	|	msg3:	agentA -> agentB	(keyB, nonceB, 0, 0)

	Note that sending (keyB, agentA, nonceA) from agentA to agentB 
	over the network (chan)nel models agentA encrypting the message 
	"[agentA, nonceB]" with agentB's public key.
*/

mtype = {
	/* Status Codes */
	ok, 
	err, 

	/* Message Codes */
	msg1, 
	msg2, 
	msg3, 

	/*	Agent (A)lice */
	keyA,
	agentA, 
	nonceA,

	/*	Agent (B)ob */
	keyB, 
	agentB,
	nonceB,

	/*	Agent (I)ntruder */
	keyI, 
	agentI, 
	nonceI 
};


/**
	Declare a structured data type to model our encrypted messages.
	Messages will contain either 2 or 3 content fields.
*/
typedef Crypt { 
	mtype key, 
	content1, 
	content2,
	content3 
};


/**
	Model network between agents via a rendezvous channel. 
	Send and recieve operations are performed synchronously. 
*/
chan network = [0] of {mtype, mtype, Crypt};


/* global variables for verification*/
mtype partnerA; 
mtype partnerB;
mtype statusA = err;
mtype statusB = err;
bool knows_nonceA; 
bool knows_nonceB;

/* Agent (A)lice */
active proctype Alice() {

	/* local variables */
	mtype pkey;			/* reciever's public key */
	mtype pnonce;		/* reciever's nonce	 */
	Crypt messageAB;	/* sent messages			 */
	Crypt data;			/* recieved messages	 */

	/* 
		Initialization: In this example we non-deterministically choose between 
		agents (B)ob and (I)ntruder
	*/
	if 
	:: partnerA = agentB; pkey = keyB;
	:: partnerA = agentI; pkey = keyI;
	fi;

	/* prepare (msg1) */
	messageAB.key = pkey;
	messageAB.content1 = agentA;
	messageAB.content2 = nonceA;
	messageAB.content3 = 0;

	/* send (msg1) */
	network ! msg1 (partnerA, messageAB);

	/* recv (msg2) : blocking */
	network ? (msg2, agentA, data);

	/* verify (msg2) : blocking	*/
	(data.key == keyA) && (data.content1 == partnerA) && (data.content2 == nonceA);

	/* obtain (msg2) sender's nonce */
	pnonce = data.content3;

	/* prepare (msg3) */
	messageAB.key = pkey;
	messageAB.content1 = pnonce;
	messageAB.content2 = 0;
	messageAB.content3 = 0;

	/* send (msg3) */
	network ! msg3 (partnerA, messageAB);

	/* update status */
	statusA = ok;
}

/* Agent (B)ob */
active proctype Bob() {
		
	/* local variables */
	mtype pkey;			/* reciever's public key */
	mtype pnonce;		/* reciever's nonce	 */
	Crypt messageBA;	/* sent messages			 */
	Crypt data;			/* recieved messages	 */

	/* Initialization	*/
	partnerB = agentA;
	pkey	 = keyA;

	/* recv (msg1) : blocking */
	network ? (msg1, agentB, data)

	/* verify (msg1) : blocking */
	(data.key == keyB) && (data.content1 == agentA);

	/* obtain (msg1) sender's nonce */
	pnonce = data.content2;	

	/* prepare (msg2) */
	messageBA.key = pkey;
	messageBA.content1 = agentB;
	messageBA.content2 = pnonce;
	messageBA.content3 = nonceB;

	/* send (msg2) */
	network ! msg2 (partnerB, messageBA);

	/* recv (msg3) : blocking */
	network ? (msg3, agentB, data);

	/* verify (msg3) : blocking */
	(data.key == keyB) && (data.content1 == nonceB);

	statusB = ok;
}

/* Agent (I)ntruder */
active proctype Intruder() {

	mtype msg, recpt;
	Crypt data, intercepted;

	/* Initialize knows_nonce variables to false */
	knows_nonceA = false;
	knows_nonceB = false;

	do
	:: network ? (msg, _, data) -> 
	if /* perhaps store the message */
			
		::	intercepted.key		 = data.key;
			intercepted.content1 = data.content1;
			intercepted.content2 = data.content2;
			
			/*	
				Message contains (I)ntruder's (public) key, the intruder can 
				decrypt the message. Note that we can learn nonce values from 
				either content1 or content2
				|	msg1:	(keyB, agentA, nonceA, 0)
				|	msg2:	(keyA, agentB, nonceA, nonceB)
				|	msg3: 	(keyB, nonceB, 0, 0)
			*/
			if 
			::  (intercepted.key == keyI) -> if 
				:: intercepted.content1 == nonceA -> knows_nonceA = true; 
				:: intercepted.content1 == nonceB -> knows_nonceB = true; 
				:: intercepted.content2 == nonceA -> knows_nonceA = true; 
				:: intercepted.content2 == nonceB -> knows_nonceB = true; 
				:: intercepted.content3 == nonceA -> knows_nonceA = true; 
				:: intercepted.content3 == nonceB -> knows_nonceB = true;
				fi 

			:: skip;
			fi 

		:: skip;
	fi;

	:: /* Replay or send a message */
	if /* choose message type */
		:: msg = msg1;
		:: msg = msg2;
		:: msg = msg3;
	fi;
		
	if /* choose a recepient */
		:: recpt = agentA;
		:: recpt = agentB;
	fi;
			 
	if /* replay intercepted message or assemble it */
		::	data.key		= intercepted.key;
			data.content1	= intercepted.content1;
			data.content2	= intercepted.content2;
			data.content3	= intercepted.content3;
		
		:: 
		if /* assemble content1 */
			:: data.content1 = agentA;
			:: data.content1 = agentB;
			:: data.content1 = agentI;
			:: (knows_nonceA) -> data.content1 = nonceA
			:: (knows_nonceB) -> data.content1 = nonceB
			:: (!knows_nonceA && !knows_nonceB) -> data.content1 = nonceI;
		fi;
		
		if /* assemble key */
			:: data.key = keyA;
			:: data.key = keyB;
			:: data.key = keyI;
		fi;
		
		if 
		:: (knows_nonceA) -> data.content2 = nonceA
		:: (knows_nonceB) -> data.content2 = nonceB
		:: (!knows_nonceA && !knows_nonceB) -> data.content2 = nonceI;
		fi 

		if 
		:: (knows_nonceA) -> data.content3 = nonceA
		:: (knows_nonceB) -> data.content3 = nonceB
		:: (!knows_nonceA && !knows_nonceB) -> data.content3 = nonceI;
		fi 
		
	fi;

	network ! msg (recpt, data);
	od
}

/**
	Always, one process will terminate in error
*/
ltl alwaysErr { [] ( (statusA == err) || (statusB == err) ) }

/**
	Eventually the protocol will complete without error
*/
ltl eventuallyOk { <> ( (statusA == ok) && (statusB == ok) ) }

/*
	propAB: If both Alice and Bob reach the end of their runs (i.e. both statusA and statusB are ok) 
	then Alice's communication partner is Bob, and Bob's communication partner is Alice.
*/
ltl propAB { [] ( ( (statusA == ok) && (statusB == ok) ) -> ( (partnerB == agentA) && (partnerA == agentB) ) ) }

/*
	propA: If agent A reaches the end of its run (statusA is ok) and A believes it is talking to B 
	(partnerA is agentB) then the intruder does not know A's nonce (!knows_nonceA).
*/
ltl propA { [] ( ( (statusA == ok)  && (partnerA == agentB) ) ->  ( knows_nonceA == false ) ) }

/*
	propB: If agent B reaches the end of its run (statusB is ok) and it believes it is talking to A 
	(partnerB is agentA) then the intruder does not know B's nonce (!knows_nonceB)
*/
ltl propB { [] ( ( (statusB == ok)  && (partnerB == agentA) ) ->  ( knows_nonceB == false ) ) }