验证BCL可靠性协议C version1.0[Incomplete]

/*
* PROMELA Validation Model
* 协议层验证
*
* 验证BCL可靠性协议C version1.0
*
* Yuhuang
* 2007-7-29
*/





/*
* 本版本特点:采用集合点来通信,允许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 */

/*
#define inc(x) (x=(x+1)%WIN)
*/

/*  消息类型定义  */
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;

/*  发送队列元素状态标志  */
bool sflag[WIN];    
/*  = 0,已确认;=1,未确认   */
/*  发送窗口的范围是由head_seq和QSIZE一起决定的  */
/*  Avaliable Range: head_seq ~ head_seq+QSIZE-1  */


/*  接收方期望系列号  */
byte expected_seqno;
/*  接收方窗口  */
/*  min , max  */
byte min_seq;
byte max_seq;
byte rseqno;    
/*  recv seqno  */
/*  未收到消息记录链  */
bool rflag[WIN];    
/*  =1表示未收到, =0 表示已经收到 */


/*  进程说明:
 * 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  ->
        
/*  CHK语义实现  */
        
        
/* CHK的完整语义为:
        检查CHK携带的seqno之前(含)的数据包是否发送到位.通过硬件相关技术,
        它可以严格保证在CHK发出之前的所有数据包都已经丢失或者到达,
        而不会存在某个数据包因为延迟而后于CHK包到达。
        
*/
        i
= 0 ;
        j
= 0 ;
        
if
        
:: (type == CHK)  ->
            
do
            
::  i  <  QSIZE  ->
                
if
                
:: flag[i]  !=   0   ->      /* 当前Msg为一个不为空的阻塞Msg */
                    
if      /*  随机选择是发送还是丢弃  */
                    
:: rdata ! msg[i] . type , msg[i] . version , msg[i] . seqno     /*  发送  */
                    
:: skip     /* 丢弃  */
                    fi;
                    flag[i] 
=   0 ;     /*  标记当前消息槽可用  */
                
:: else  
                fi;
                i 
=  i  +   1 ;
            
::   else   break
            od;            
        
:: else   -> skip
        fi;
/* ====================================================== */     
        
/*  阻塞语义实现  */
        
        
/*  不为空者逐个加一  */
        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   ->      /* 选择到一个不为空的阻塞Msg */
                
if      /*  随机选择是发送还是丢弃  */
                
:: rdata ! msg[i] . type , msg[i] . version , msg[i] . seqno     /*  发送  */
                
:: skip     /* 丢弃  */
                fi;                
                flag[i] 
=   0 ;     /*  标记当前消息槽可用  */
                
break ;
            
:: 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 endofsend1    
/*  信息已经阻塞,无须发送  */
                
            
:: else ->      /*  Msg[i]中已有一条消息,且不阻塞这条消息了  */
                
if
                
:: rdata ! type , v , seq
                
:: skip
                fi
            fi;
endofsend1
:         skip
        
:: skip  ->      /*  直接传递或丢弃数据包  */         
            
if
            
:: rdata ! type , v , seq
            
:: skip
            fi
        fi
    od
}


proctype receiver2media()
{
    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
    
:: revts ? type , v , seq  ->      /*  从receiver方接到消息  */

        
/*  不为空者逐个加一  */
        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
                
:: sevts ! 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   ->      /* 选择到一个不为空的阻塞Msg */
                
if      /*  随机选择是发送还是丢弃  */
                
:: sevts ! msg[i] . type , msg[i] . version , msg[i] . seqno     /*  发送  */
                
:: skip     /* 丢弃  */
                fi;
                flag[i] 
=   0 ;     /*  标记当前消息槽可用  */
                
break ;
            
:: 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 endofsend2    
/*  信息已经阻塞,无须发送  */
                
            
:: else ->      /*  Msg[i]中已有一条消息,且不阻塞这条消息了  */
                
if
                
:: sevts ! type , v , seq
                
:: skip
                fi
            fi;
endofsend2
:         skip
        
:: skip  ->      /*  直接传递或丢弃数据包  */         
            
if
            
:: sevts ! type , v , seq
            
:: skip
            fi
        fi
    od

}



/*
* 发送方
* 发送两类消息: DATA(数据) 、CHK( 缓冲区可用性检查) 

* 接受两类消息: ACK( 确认) 、RRT(请求重传) 
*/
proctype sender()
{

    byte s;
    byte version;
    int rrt_version;

    byte i;
    byte j;
    
    
/*  发送队列 */
    Msg sbuf[WIN];    
/*  为了方便,不使用QSIZE。实际上,某个时刻起作用的个数仍然为QSIZE个  */

    
    
/*  假设第0个包已经发出  */
    head_seq 
=   0 ;
    tail_seq 
=   0 ;
    sflag[
0 =   1 ;
    
    cur_seq 
=   0 ;     /* 定位发送点  */
    
    rrt_version 
=   0 /*  init rrt version number  */
    
    
    
do
    
:: (tail_seq + WIN - head_seq + 1 ) % WIN  <  QSIZE  ->      /* * 队列不为满 (没有超出窗口范围),允许加入新的待发消息 * */
        
/* 加入一个元素到发送队列中  */
progress_2
:     tail_seq  =  (tail_seq  +   1 ) % WIN; /*  更新tail,  head保持不变  */
        sflag[tail_seq] 
=   1 ;
        
/*  立即发送之  */
        sdata
! DATA , 0 , tail_seq;     /*  try to send now!  */

        
    
:: sevts ? ACK , version , ->              /* * 接收到ACK * */
        
/*  无须进行ACK失序处理  */
        
assert (s < WIN);
        
/*  ACK的时候需要检查是否可以向前滑动窗口(这里头变尾不变)  */
        i
= s;
        sflag[i] 
=   0 ;     /*  标志sflag[s] 已经确认  */
        
if
        
:: i == head_seq  ->      /* 可以滑动,至少一格,多则数格  */
            
do
            
:: sflag[i] == 0   ->      /*  此元素已经确认  */
                i 
=  (i  +   1 ) % WIN;
            
:: else   ->
                
/*  将队列前面连续的都确认了的消息从窗口范围内删除。 */
                head_seq 
=  i;  /*  got new head,此时do的第一个分支又可以工作了  */
                
break ;
            od
        
:: else
        fi
        
        
        
    
:: sevts ? RRT , version , ->              /* * 接收到RRT * */
        
if
        
:: (version  ==  rrt_version)  ->      /*  预期rrt */
            
/* 重发s,rrt_version加1   */
            sdata
! DATA , 0 , i;
            rrt_version 
=  (rrt_version  +   1 %  WIN;     /*  inc(rrt_version);  */
            
        
::   else   ->      /*  接受到非预期rrt  */
            skip    
/*  简单丢弃之  */
        fi
    
    
:: timeout  ->      /*  超时  */
        
/*  扫描发送队列,找到第一个可以发送的消息,对其发出CHK  */
        i 
=  head_seq;
        j 
=  tail_seq;
        
do
        
::  i  <=  tail_seq  ->
            
if
            
:: (sflag[i]  ==   1 ->
                sdata
! CHK , rrt_version , i;
                
break ;
            
:: else   ->  skip
            fi;
            i 
=  (i  +   1 ) % WIN;
        
::   else   ->         
            
break ;
        od;
    od
}



/*
* 接收方
* 发送两类消息: ACK( 确认) 、RRT(请求重传) 

* 接受两类消息: DATA(数据) 、CHK( 缓冲区可用性检查) 
*/
proctype receiver()
{


    byte v;

    
    
/*  假设0号消息已经收到  */
    max_seq 
=   0 ;
    min_seq 
=   0 ;
    rflag[
0 =   0 ;
    
    
do
    
:: rdata ? CHK , v , rseqno  ->      /* * 收到CHK消息 * */
        
if
        
:: ((min_seq + WIN - rseqno - 1 ) % WIN  <  WIN / 2 ->          /*  seqno < min_seqno  */
            revts
! ACK , v , rseqno;
        
:: ((rseqno - max_seq + WIN - 1 ) % WIN  <  WIN / 2 ->          /*  Seqno > max_seqno  */
            revts
! RRT , v , rseqno;
        
:: else                          /*   min_seqno < seqno < max_seqno  */
            
/* 检查s是否已经接收到 */
            
if
            
:: rflag[rseqno] == 1   ->      /* 未收到 */
                revts
! RRT , v , rseqno;
            
:: else   ->      /* 已收到 */
                revts
! ACK , v , rseqno;
            fi
        fi



    
:: rdata ? DATA , v , rseqno  ->      /* * 收到DATA消息,s为消息序列号 * */
        
if
        
:: ((rseqno - min_seq + WIN - 1 ) % WIN  <  WIN / 2   &&  (max_seq - rseqno + WIN - 1 ) % WIN  <  WIN / 2 ->      /*  s在二者之间  */
            rflag[rseqno] 
=   0 ;     /*  标志为已经收到 */
            revts
! ACK , v , rseqno;
            
/*  尝试更新 min_seq  */
            rseqno
= min_seq;
            
do
            
:: rflag[rseqno] == 0   ->
                
if
                
/* ::error  (max_seq-rseqno+WIN-1)%WIN < WIN/2 -> */
                
:: (max_seq - rseqno + WIN) % WIN  <=  WIN / 2   ->
                    rseqno 
=  (rseqno  +   1 ) % WIN;
                
:: else   ->   break ;
                fi
            
:: else   ->
                
break ;
            od;
            
            min_seq 
=  rseqno - 1 ;
            
        
:: ((rseqno - max_seq + WIN - 1 ) % WIN  <=  WIN / 2 ->          /*  seqno > max_seqno  */
            
/*  将max_seq到s之间的数据全部标记为未收到  */
            
do
            
:: max_seq  <  rseqno  ->
                max_seq 
=  (max_seq  +   1 ) % WIN;
                rflag[max_seq] 
=   1 ;     /* 标记未收到 */
            
:: else   ->
                
break ;
            od;
            rflag[max_seq]  
=   0 ;     /*  max_seq已经更新成了s,标记为已经收到  */
        
:: else   ->      /*  seqno = max_seqno || seqno <= min_seqno  */
            
assert ( false   &&   1 == 0 );     /*  deadly fault  *//* 收到重复消息 */
        fi        
    od
}


/*  Promela入口程序  */
init
{
    atomic{
    run sender();
    run sender2media();
    run receiver();
    run receiver2media();
    }

}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值