/*
* 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 , s -> /* * 接收到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 , s -> /* * 接收到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();
}
}
* 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 , s -> /* * 接收到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 , s -> /* * 接收到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();
}
}