/** PROMELA Validation Model* 协议层验证** 验证BCL可靠性协议B version4.0** Yuhuang* 2007-7-27*//** 本版本特点:采用集合点来通信,允许CHK、RTT、ACK、DATA丢包,实现了队列自动重发。* 加入了对传输介质的模拟。介质可以丢包,导致包失序。* 加入了对传输介质中存在延迟阻塞的模拟。包可以失序。* * 加入对CHK的语义实现、接收方向发送方的失序模拟。 *//** 测试记录: * WIN=5 QSIZE=2 跑了大约_秒钟* WIN=7 QSIZE=3 跑了大约_分钟* WIN=9 QSIZE=4 ???*//************** 数据结构定义 *************//* 序号范围定义 */#define WIN 7/* 发送队列大小定义 */#define QSIZE 3/*Note:上面是比较保险的做法: WIN = QSIZE * 2 + 1*//* 消息类型定义 */mtype = { ACK,DATA,RRT,CHK }/* * { ACK|RRT,res|version, seqno } */chan sevts = [0] of { mtype,byte,byte };chan revts = [0] of { mtype,byte,byte };/* * { DATA|CHK, res|version, seqno} * 数据队列 */chan sdata = [0] of { mtype,byte,byte };chan rdata = [0] of { mtype,byte,byte };/* * 为实现阻塞模拟,需要定义复合类型数组 */typedef Msg{ mtype type; byte version; byte seqno;};/* 发送记录队列 *//* 当ACK发生的时候,ACK(含)之前的内容都被清除 */byte head_seq;byte tail_seq;byte cur_seq;/*#define inc(x) (x=(x+1)%WIN)*//* 接收方期望系列号 */byte expected_seqno;/* 进程说明: * 1. sender2media 发送方到接收方的介质模拟进程 * 2. receiver2media 接收方到发送方的介质模拟进程 * 3. sender 发送方进程 * 4. receiver 接收方进程 */proctype sender2media(){ byte seq; byte v; mtype type; /* 阻塞队列 */ Msg msg[QSIZE]; /* flag语义: * flag[i] = 0的时候,表示msg[i] 为空 * flag[i] = m (m>0) 的时候,表示从msg[i]被阻塞以来,没有被阻塞的包的个数为m * flag用法: * (1)每次收到一个消息,将不为零的flag[i]逐个加一 * (2)发现某个flag[i] 的值大于等于QSIZE时必须将其从阻塞队列中取出,进行发送或丢弃,转4 * (3)随机选择一个不为空的阻塞Msg,将其从阻塞队列中取出,进行发送或丢弃 * (4)flag子序列结束 */ byte flag[QSIZE]; byte i; /* 记录随机数下标用 */ byte j; /* 产生随机数用 */ do ::sdata?type,v,seq -> /* 不为空者逐个加一 */ i = 0; j = 0; do :: i < QSIZE -> if ::flag[i] != 0 -> flag[i] = flag[i] + 1 ::else -> skip fi; i = i + 1 :: else break od; /*寻找需要立即处理的Msg */ i = 0; j = 0; do :: i < QSIZE -> if ::(flag[i] == QSIZE-1) -> if ::rdata!msg[i].type,msg[i].version,msg[i].seqno-> flag[i] = 0 fi ::else -> skip fi; i = i + 1; :: else break od; /* 随机选择一个不为空的阻塞Msg,将其从阻塞队列中取出,进行发送或丢弃*/ i = 0; j = 0; do :: i < QSIZE -> if ::flag[i] != 0 -> if ::rdata!msg[i].type,msg[i].version,msg[i].seqno -> flag[i] = 0; ::skip fi ::skip fi; i = i + 1 :: else break od; /* 阻塞或传递当前的数据包 */ i=0; j=0; if ::skip -> /* 阻塞这个数据包*/ /* 随机选择一个空位 */ do ::j>=0 -> j = (j + 1) % QSIZE ::skip -> i = j; /*获得随机数 */ break od; if ::flag[i]==0 -> /*Msg[i]为空,可以放下一条阻塞消息*/ msg[i].type = type; msg[i].version = v; msg[i].seqno = seq; flag[i] = 1; /*标记Msg[i] 中已经存有消息*/ goto endofsend /* 信息已经阻塞,无须发送 */ ::else-> /* Msg[i]中已有一条消息,且不阻塞这条消息了 */ if ::rdata!type,v,seq ::skip fi fi;endofsend: skip ::skip -> /* 直接传递或丢弃数据包 */ if ::rdata!type,v,seq ::skip fi fi od}proctype receiver2media(){ byte seq; byte v; mtype type; do ::revts?type,v,seq -> if ::sevts!type,v,seq ::skip fi od}/** 发送方* 发送两类消息: DATA(数据) 、CHK( 缓冲区可用性检查) * * 接受两类消息: ACK( 确认) 、RRT(请求重传) */proctype sender(){ byte s; byte version; byte rrt_version; head_seq = 0; tail_seq = 0; cur_seq = 0; rrt_version = 0; /* init rrt version number */ do ::(tail_seq+WIN-head_seq+1)%WIN != QSIZE -> /* 队列不为满,允许发送*/ if ::sdata!DATA,0,cur_seq /* normal send action */ fi; progress_2: cur_seq=(cur_seq+1)%WIN; tail_seq=(tail_seq+1)%WIN;; /* 更新tail, head保持不变 */ ::sevts?ACK,version,s -> /* 进行ACK失序处理 */ /* 若s不在当前的head_seq~tail_seq范围内,则简单忽略之 */ if ::(head_seq < tail_seq) -> /*顺序递增情景 */ if ::(s > tail_seq || s < head_seq ) -> goto endofack ::else fi ::(head_seq > tail_seq) -> /* 非递增情景 */ if ::( s < head_seq && s > tail_seq ) -> goto endofack ::else fi ::else -> /* 队列为空 */ goto endofack /* 此ACK是在介质中被延迟了的ACK, 简单丢弃之 */ fi; assert(s<WIN); head_seq = (s + 1)%WIN; /* 根据ACK更新head_seq,暗含了累积更新 */ cur_seq = head_seq; endofack: skip ::sevts?RRT,version,s -> if ::(version == rrt_version) -> /* 预期rrt*/ /* 将发送链上s(eqno)之前的数据都摘除 */ /*发送链上 s之后的所有数据都重发,rrt_version加1 */ head_seq = s; /* 注意: 由于是响应 RRT消息,不要写成了head_seq =(s + 1)%WIN */ cur_seq = head_seq; /* 调整发送指针,准备重发 */ rrt_version = (rrt_version + 1) % WIN /* inc(rrt_version); */ :: else -> /* 接受到非预期rrt */ skip /* 简单丢弃之 */ fi ::((cur_seq != tail_seq) && (tail_seq - head_seq) != 0 ) -> /* 队列不为空,允许发送. 此情景配合timeout时重发使用 */ /* 第一个判断是为了防止发送不存在内容 */ if ::sdata!DATA,0,cur_seq /* normal send action */ fi; cur_seq=(cur_seq+1)%WIN; ::timeout -> /* 超时 */ sdata!CHK,rrt_version,head_seq; /* 超时,发CHK消息,查询是否需要重发 */ od}/** 接收方* 发送两类消息: ACK( 确认) 、RRT(请求重传) * * 接受两类消息: DATA(数据) 、CHK( 缓冲区可用性检查) */proctype receiver(){ byte s; byte v; do ::rdata?CHK,v,s -> /*收到CHK消息*/ if ::(s == expected_seqno) -> /* chk检查的数据包即为接收方期望的数据包 */ revts!RRT,v,s /* 直接返回RRT消息 */ ::( ( s < expected_seqno && expected_seqno-s < WIN/2 ) || ( s > expected_seqno && s-expected_seqno > WIN/2) ) -> /* 小于期望包号,ACK之 */ revts!ACK,0,(expected_seqno+WIN-1)%WIN ::else -> /* CHK检查消息错,系统出错 */ printf("s=%d exp=%d ",s,expected_seqno); assert(false) fi ::rdata?DATA,v,s -> /* 收到DATA消息,s为消息序列号 */ /* s 与expected_seqno在队列中前后关系的判断: 1. if ( s > expected_seqno && s-expected_seqno < WIN/2 ) or *normal ( s < expected_seqno && expected_seqno-s > WIN/2) then s'>'expected_seqno -- 收到的消息(s)排在期待数(expected_seqno)的后面 2. if ( s < expected_seqno && expected_seqno-s < WIN/2 ) or *normal ( s > expected_seqno && s-expected_seqno > WIN/2) then s'<'expected_seqno -- 收到的消息排在期待数的前面 3. if( s==expected_seqno ) then s'=='expected_seqno -- 收到的消息等于期待的数 */ if ::( ( s > expected_seqno && s-expected_seqno < WIN/2 ) || ( s < expected_seqno && expected_seqno-s > WIN/2) ) -> /*大于*/ /* 在存在丢包的流水环境下,这种情况是完全会出现的,不应该看作系统严重错误*/ /*assert(false) *//* 非预期消息,系统严重错误 */ /* Yuhuang的方案(2007-7-27): 简单忽略之 */ skip ::( s == expected_seqno) -> /*等于*/ revts!ACK,0,expected_seqno; expected_seqno = (expected_seqno+1)%WIN :: else -> /*小于*/ revts!ACK,0,(expected_seqno+WIN-1)%WIN fi od }/* Promela入口程序 */init{ atomic{ run sender(); run sender2media(); run receiver(); run receiver2media(); }}