Skip to content

Commit 17bcb99

Browse files
committed
CI test switch mode
1 parent 53f6d9e commit 17bcb99

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

.github/workflows/MiniPlex.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ jobs:
7979
build/MiniPlex -P -p 20002 -r 127.0.0.1 -t 50000 &
8080
build/MiniPlex -P -p 20003 -r 127.0.0.1 -t 50000 -B 127.0.0.1 -b 50001 &
8181
build/MiniPlex -T -p 20004 -r 127.0.0.1 -t 50000 -B 127.0.0.1 -b 50001 &
82+
build/MiniPlex -X -p 20005 -X -C Examples/SwitchBytecode/SwitchDNP3_CRC.bin &
8283
socat pty,raw,echo=0,link=SerialEndpoint1 pty,raw,echo=0,link=SerialEndpoint2 &
8384
socat pty,raw,echo=0,link=SerialEndpoint3 pty,raw,echo=0,link=SerialEndpoint4 &
8485
socat pty,raw,echo=0,link=SerialEndpoint5 pty,raw,echo=0,link=SerialEndpoint6 &
@@ -105,6 +106,11 @@ jobs:
105106
run: |
106107
Test/PruneMode.sh 50000 127.0.0.1 20002
107108
109+
- if: ${{ always() && !contains(matrix.os,'windows') }}
110+
name: Test Switch Mode
111+
run: |
112+
Test/DNP3SwitchMode.pl
113+
108114
- if: ${{ always() && !contains(matrix.os,'windows') }}
109115
name: Test Permanent Branches Prune
110116
run: |

Test/DNP3SwitchMode.pl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/perl
2+
use strict;
3+
use warnings;
4+
use IPC::Open3;
5+
use Symbol qw(gensym);
6+
7+
my %procs = (
8+
'01' => { command => './SendDNP3.pl 20011 127.0.0.1 20005 01 02 50 10' },
9+
'02' => { command => './SendDNP3.pl 20012 127.0.0.1 20005 02 01 50 10' },
10+
'03' => { command => './SendDNP3.pl 20013 127.0.0.1 20005 03 04 50 10' },
11+
'04' => { command => './SendDNP3.pl 20014 127.0.0.1 20005 04 03 50 10' },
12+
'05' => { command => './SendDNP3.pl 20015 127.0.0.1 20005 05 06 50 10' },
13+
'06' => { command => './SendDNP3.pl 20016 127.0.0.1 20005 06 05 50 10' },
14+
'07' => { command => './SendDNP3.pl 20017 127.0.0.1 20005 07 08 50 10' },
15+
'08' => { command => './SendDNP3.pl 20018 127.0.0.1 20005 08 07 50 10' },
16+
'09' => { command => './SendDNP3.pl 20019 127.0.0.1 20005 09 10 50 10' },
17+
'10' => { command => './SendDNP3.pl 20020 127.0.0.1 20005 10 09 50 10' },
18+
);
19+
20+
# Launch all commands
21+
for my $id (keys %procs) {
22+
my $p = $procs{$id};
23+
($p->{in}, $p->{out}, $p->{err}) = (undef, undef, gensym());
24+
$p->{pid} = open3($p->{in}, $p->{out}, $p->{err}, $p->{command});
25+
}
26+
27+
# Wait and collect output
28+
for my $id (keys %procs) {
29+
my $p = $procs{$id};
30+
waitpid($p->{pid}, 0);
31+
$p->{stdout} = do { local $/; readline($p->{out}) };
32+
$p->{stderr} = do { local $/; readline($p->{err}) };
33+
$p->{exit} = $? >> 8;
34+
}
35+
36+
# Display results
37+
my $success = 1;
38+
for my $id (sort keys %procs) {
39+
my $p = $procs{$id};
40+
print "[$id] (exit: $p->{exit})\n";
41+
print "STDOUT:\n$p->{stdout}\n" if $p->{stdout};
42+
print "STDERR:\n$p->{stderr}\n" if $p->{stderr};
43+
44+
$p->{stdout} =~ /^0x[0-9A-F]{4}->(0x[0-9A-F]{4}) : ([0-9]+)/;
45+
my $top_addr = hex($1);
46+
my $top_count = int($2);
47+
my $self_addr = int($id);
48+
49+
if($top_addr != $self_addr or $top_count < 49)
50+
{
51+
print "Top address ($top_addr) != Self address ($self_addr) or Top count ($top_count) < 49\n";
52+
$success = 0;
53+
}
54+
}
55+
56+
die "FAILED\n" if($success == 0);
57+
print "SUCCESS\n" if($success == 1);
58+
exit(0);

Test/SendDNP3.pl

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/perl
2+
use strict;
3+
use warnings;
4+
use IO::Socket::INET;
5+
use IO::Select;
6+
use Time::HiRes qw(sleep time);
7+
8+
if (@ARGV != 7) {
9+
die "Usage: $0 local_port dest_ip dest_port DNP3_src DNP3_dst count duration_sec\n";
10+
}
11+
12+
my ($local_port, $dest_ip, $dest_port, $DNP3_src, $DNP3_dst, $count, $duration) = @ARGV;
13+
14+
for my $p ($local_port, $dest_port) {
15+
die "Port must be integer 0..65535\n" unless $p =~ /^\d+$/ && $p >= 0 && $p <= 65535;
16+
}
17+
for my $v ($DNP3_src, $DNP3_dst) {
18+
die "DNP3 addr values must be integer 0..65535\n" unless $v =~ /^\d+$/ && $v >= 0 && $v <= 65535;
19+
}
20+
21+
#printf("DNP3_src = 0x%04X\n", $DNP3_src);
22+
#printf("DNP3_dst = 0x%04X\n", $DNP3_dst);
23+
24+
# Build payload
25+
my $DNP3_start = 0x0564;
26+
my $DNP3_ctl_len = 0x05c9;
27+
my $payload = pack('n n v v', $DNP3_start, $DNP3_ctl_len, $DNP3_dst, $DNP3_src);
28+
29+
# Compute CRC
30+
my $crc = computeCRC($payload);
31+
32+
# Debug: print CRC
33+
#printf("CRC = 0x%04X\n", $crc);
34+
35+
# Append CRC
36+
$payload .= pack('v', $crc);
37+
38+
# Debug: print final buffer as hex
39+
#print "Final buffer (hex): ";
40+
#print unpack("H*", $payload), "\n";
41+
42+
# Create UDP socket
43+
my $sock = IO::Socket::INET->new(
44+
Proto => 'udp',
45+
LocalPort => $local_port,
46+
PeerAddr => $dest_ip,
47+
PeerPort => $dest_port,
48+
) or die "Cannot create UDP socket: $!\n";
49+
50+
# Statistics tracking
51+
my %packet_counts; # Key: "src:dst", Value: count
52+
my $total_received = 0;
53+
my $total_sent = 0;
54+
my $crc_errors = 0;
55+
56+
# Create select object for non-blocking reads
57+
my $select = IO::Select->new($sock);
58+
59+
# Fork to handle sending and receiving concurrently
60+
my $pid = fork();
61+
die "Fork failed: $!\n" unless defined $pid;
62+
63+
if ($pid == 0) {
64+
# Child process: Send packets
65+
for my $i (1..$count) {
66+
my $sent = $sock->send($payload);
67+
if (defined $sent) {
68+
#print "Sent packet $i ($sent bytes) to $dest_ip:$dest_port\n";
69+
$total_sent++;
70+
} else {
71+
warn "Send $i failed: $!\n";
72+
}
73+
sleep($duration/$count) if $i < $count; # Don't sleep after last packet
74+
}
75+
exit(0);
76+
} else {
77+
# Parent process: Receive packets
78+
my $last_packet_time = time();
79+
my $timeout = 2.0; # 2 seconds of silence
80+
81+
while (1) {
82+
my $elapsed = time() - $last_packet_time;
83+
my $remaining = $timeout - $elapsed;
84+
85+
# Check if we've had 2 seconds of silence
86+
if ($remaining <= 0) {
87+
last;
88+
}
89+
90+
# Wait for data with remaining timeout
91+
my @ready = $select->can_read($remaining);
92+
93+
if (@ready) {
94+
my $recv_data;
95+
my $peer_addr = $sock->recv($recv_data, 1024);
96+
97+
if (defined $peer_addr) {
98+
$last_packet_time = time();
99+
$total_received++;
100+
101+
my ($peer_port, $peer_ip) = sockaddr_in($peer_addr);
102+
my $peer_ip_str = inet_ntoa($peer_ip);
103+
104+
# Parse the received packet
105+
if (length($recv_data) >= 10) {
106+
my ($start, $ctl_len, $dst, $src, $recv_crc) = unpack('n n v v v', $recv_data);
107+
108+
# Verify CRC
109+
my $data_for_crc = substr($recv_data, 0, 8);
110+
my $computed_crc = computeCRC($data_for_crc);
111+
112+
if ($computed_crc == $recv_crc) {
113+
#printf("Received valid packet from %s:%d - DNP3 src=0x%04X dst=0x%04X\n",
114+
# $peer_ip_str, $peer_port, $src, $dst);
115+
116+
# Track statistics
117+
my $key = sprintf("0x%04X->0x%04X", $src, $dst);
118+
$packet_counts{$key}++;
119+
} else {
120+
printf("Received packet with CRC error from %s:%d (expected 0x%04X, got 0x%04X)\n",
121+
$peer_ip_str, $peer_port, $computed_crc, $recv_crc);
122+
$crc_errors++;
123+
}
124+
} else {
125+
printf("Received short packet from %s:%d (%d bytes)\n",
126+
$peer_ip_str, $peer_port, length($recv_data));
127+
}
128+
}
129+
}
130+
}
131+
132+
# Wait for child to finish
133+
waitpid($pid, 0);
134+
135+
for my $addr (sort {$packet_counts{$b}<=>$packet_counts{$a}} keys %packet_counts) {
136+
printf("%s : %d\n", $addr, $packet_counts{$addr});
137+
}
138+
$sock->close();
139+
}
140+
141+
142+
# ----------------- CRC FUNCTION -----------------
143+
sub computeCRC {
144+
my ($buf) = @_;
145+
my @bytes = unpack('C*', $buf);
146+
147+
my $crc = 0;
148+
for my $dataOctet (@bytes) {
149+
for (0..7) {
150+
my $temp = ($crc ^ $dataOctet) & 1;
151+
$crc >>= 1;
152+
$dataOctet >>= 1;
153+
if ($temp) {
154+
$crc ^= 0xA6BC;
155+
}
156+
}
157+
}
158+
$crc = (~$crc) & 0xFFFF;
159+
return $crc;
160+
}

0 commit comments

Comments
 (0)