المصدر المنشور
تنزيل الشيفرة المصدرية بصيغة ZIP
CH-J SM Mobile
تصفّح الأدلة والملفات الخاصة بإصدار محدد من التطبيق.
CH-J / SM Mobile
175 ملفات
sm-mobile-20261016T185235Z-8e93ecc1b59a
تاريخ الإضافة: · تاريخ البناء:
1
import 'dart:async';2
import 'dart:io';3
import 'dart:typed_data';4
import 'package:flutter_test/flutter_test.dart';5
import 'package:sm_mobile/services/net/icmp_packet.dart';6
import 'package:sm_mobile/services/net/icmp_probe.dart';7
import 'package:sm_mobile/services/net/posix.dart';9
/// IPv4 header (20 bytes, protocol ICMP) in front of [icmp].10
Uint8List ipv4(List<int> icmp, {int protocol = 1}) => Uint8List.fromList([11
0x45, 0, 0, 0, 0, 0, 0, 0, 64, protocol, 0, 0, //12
192, 0, 2, 1, 192, 0, 2, 2, ...icmp,13
]);15
/// IPv6 header (40 bytes, next header ICMPv6) in front of [icmp].16
Uint8List ipv6(List<int> icmp, {int next = 58}) => Uint8List.fromList([17
0x60,18
0,19
0,20
0,21
0,22
8,23
next,24
64,25
...List.filled(32, 0),26
...icmp,27
]);29
Future<List<ProbeEvent>> collect(IcmpRun run) =>30
run.events.toList().timeout(const Duration(seconds: 60));32
void main() {33
group('ICMP packets', () {34
test('echo request layout and RFC 1071 checksum', () {35
final p = buildEchoRequest(v6: false, id: 0x1234, seq: 7, size: 4);36
expect(p.sublist(0, 2), [8, 0]);37
expect(p.sublist(4, 8), [0x12, 0x34, 0, 7]);38
expect(icmpChecksum(p), 0, reason: 'a valid packet sums to zero');39
final v6 = buildEchoRequest(v6: true, id: 1, seq: 2, size: 0);40
expect(v6[0], 128);41
expect(v6.length, 8);42
// RFC 1071 example data.43
expect(44
icmpChecksum(45
Uint8List.fromList([0, 1, 0xf2, 3, 0xf4, 0xf5, 0xf6, 0xf7]),46
),47
~0xddf2 & 0xffff,48
);49
expect(icmpChecksum(Uint8List.fromList([1])), ~0x0100 & 0xffff);50
});52
test('echo replies with and without the IPv4 header', () {53
final reply = [0, 0, 0, 0, 0xab, 0xcd, 0, 9];54
final darwin = parseIcmp(ipv4(reply), v6: false, ipHeader: true)!;55
expect(56
(darwin.kind, darwin.id, darwin.seq),57
(IcmpKind.echoReply, 0xabcd, 9),58
);59
final linux = parseIcmp(Uint8List.fromList(reply), v6: false)!;60
expect((linux.kind, linux.seq), (IcmpKind.echoReply, 9));61
final six = parseIcmp(62
Uint8List.fromList([129, 0, 0, 0, 0, 1, 0, 2]),63
v6: true,64
)!;65
expect((six.kind, six.id, six.seq), (IcmpKind.echoReply, 1, 2));66
});68
test('errors are matched by the quoted echo request', () {69
final quoted = ipv4([8, 0, 0, 0, 0x12, 0x34, 0, 5]);70
final exceeded = parseIcmp(71
ipv4([11, 0, 0, 0, 0, 0, 0, 0, ...quoted]),72
v6: false,73
ipHeader: true,74
)!;75
expect(76
(exceeded.kind, exceeded.id, exceeded.seq),77
(IcmpKind.timeExceeded, 0x1234, 5),78
);79
final unreachable = parseIcmp(80
Uint8List.fromList([3, 3, 0, 0, 0, 0, 0, 0, ...quoted]),81
v6: false,82
)!;83
expect((unreachable.kind, unreachable.code), (IcmpKind.unreachable, 3));84
expect(unreachable.seq, 5);85
final quoted6 = ipv6([128, 0, 0, 0, 0, 7, 0, 8]);86
final six = parseIcmp(87
Uint8List.fromList([3, 0, 0, 0, 0, 0, 0, 0, ...quoted6]),88
v6: true,89
)!;90
expect((six.kind, six.id, six.seq), (IcmpKind.timeExceeded, 7, 8));91
final unreachable6 = parseIcmp(92
Uint8List.fromList([1, 4, 0, 0, 0, 0, 0, 0, ...quoted6]),93
v6: true,94
)!;95
expect((unreachable6.kind, unreachable6.code), (IcmpKind.unreachable, 4));96
});98
test('foreign, truncated and malformed messages are not matched', () {99
expect(parseIcmp(Uint8List(3), v6: false), isNull);100
expect(101
parseIcmp(Uint8List.fromList([0x60, 0, 0]), v6: false, ipHeader: true),102
isNull,103
);104
// Time exceeded quoting a UDP packet (someone else's traceroute).105
final udp = parseIcmp(106
ipv4([107
11,108
0,109
0,110
0,111
0,112
0,113
0,114
0,115
...ipv4(List.filled(8, 0), protocol: 17),116
]),117
v6: false,118
ipHeader: true,119
)!;120
expect((udp.kind, udp.seq), (IcmpKind.timeExceeded, null));121
// Quoted packet cut short.122
expect(123
parseIcmp(124
Uint8List.fromList([11, 0, 0, 0, 0, 0, 0, 0, 0x45]),125
v6: false,126
)!.seq,127
isNull,128
);129
// ICMPv6 error quoting a non-ICMPv6 packet.130
expect(131
parseIcmp(132
Uint8List.fromList([133
3,134
0,135
0,136
0,137
0,138
0,139
0,140
0,141
...ipv6(List.filled(8, 0), next: 17),142
]),143
v6: true,144
)!.seq,145
isNull,146
);147
// Router advertisements and others are ignored.148
expect(149
parseIcmp(150
Uint8List.fromList([134, 0, 0, 0, 0, 0, 0, 0]),151
v6: true,152
)!.kind,153
IcmpKind.other,154
);155
expect(156
sentEcho(Uint8List.fromList([8, 0, 0, 0, 0, 1, 0, 2]), v6: false),157
(id: 1, seq: 2),158
);159
expect(160
sentEcho(Uint8List.fromList([0, 0, 0, 0, 0, 1, 0, 2]), v6: false),161
isNull,162
);163
});165
test('statistics: min, average, maximum and loss', () {166
final stats = PingStats.of([167
const ProbeEvent(1, ProbeStatus.reply, rttMs: 10),168
const ProbeEvent(2, ProbeStatus.timeout),169
const ProbeEvent(3, ProbeStatus.reply, rttMs: 30),170
const ProbeEvent(4, ProbeStatus.unreachable),171
]);172
expect((stats.sent, stats.received), (4, 2));173
expect((stats.min, stats.avg, stats.max), (10.0, 20.0, 30.0));174
expect(stats.lossPercent, 50);175
final none = PingStats.of(const []);176
expect((none.lossPercent, none.avg), (0.0, null));177
});179
test('socket addresses for both platform layouts', () {180
final p = Posix.current!;181
for (final a in [182
InternetAddress('192.0.2.7'),183
InternetAddress('2001:db8::7'),184
]) {185
final raw = p.sockaddr(a);186
expect(p.address(raw, raw.length), a);187
}188
});189
});191
// Real sockets on this host. On macOS this is the same Darwin socket API192
// that iOS uses; Android uses the Linux implementation (device test).193
group('real ICMP on this host (loopback)', () {194
test('ping 127.0.0.1 and ::1', () async {195
for (final target in [196
InternetAddress.loopbackIPv4,197
InternetAddress.loopbackIPv6,198
]) {199
final events = await collect(200
IcmpProbe.ping(201
target,202
const PingSettings(count: 3, interval: Duration(milliseconds: 200)),203
),204
);205
expect(events.map((e) => e.seq), [1, 2, 3], reason: '$target');206
expect(207
events.every((e) => e.status == ProbeStatus.reply),208
true,209
reason: '$target',210
);211
expect(events.every((e) => e.from == target), true);212
expect(events.every((e) => e.rttMs! >= 0 && e.rttMs! < 1000), true);213
}214
});216
test('payload sizes from 0 to 1400 bytes', () async {217
for (final size in [0, 8, 1400]) {218
final events = await collect(219
IcmpProbe.ping(220
InternetAddress.loopbackIPv4,221
PingSettings(count: 1, size: size),222
),223
);224
expect(events.single.status, ProbeStatus.reply, reason: '$size B');225
}226
});228
test('traceroute to loopback reaches the target at hop 1', () async {229
final events = await collect(230
IcmpProbe.traceroute(231
InternetAddress.loopbackIPv4,232
const TraceSettings(),233
),234
);235
expect(events.length, 3);236
expect(237
events.every((e) => e.ttl == 1 && e.status == ProbeStatus.reply),238
true,239
);240
});242
test('cancel stops at once and sends nothing more', () async {243
final run = IcmpProbe.ping(244
InternetAddress.loopbackIPv4,245
const PingSettings(count: 100, interval: Duration(seconds: 1)),246
);247
final events = <ProbeEvent>[];248
final first = Completer<void>();249
final done = Completer<void>();250
run.events.listen((e) {251
events.add(e);252
if (!first.isCompleted) first.complete();253
}, onDone: done.complete);254
await first.future.timeout(const Duration(seconds: 10));255
final watch = Stopwatch()..start();256
await run.cancel();257
await done.future.timeout(const Duration(seconds: 2));258
expect(watch.elapsedMilliseconds, lessThan(1000));259
final count = events.length;260
await Future<void>.delayed(const Duration(seconds: 2));261
expect(events.length, count, reason: 'no events after cancel');262
expect(count, lessThan(5));263
});265
test('an unanswered target times out instead of hanging', () async {266
// 192.0.2.0/24 is TEST-NET-1 (RFC 5737): never answered.267
final events = await collect(268
IcmpProbe.ping(269
InternetAddress('192.0.2.1'),270
const PingSettings(271
count: 2,272
interval: Duration(milliseconds: 200),273
timeout: Duration(milliseconds: 500),274
),275
),276
);277
expect(events.length, 2);278
expect(279
events.every(280
(e) =>281
e.status == ProbeStatus.timeout ||282
e.status == ProbeStatus.unreachable ||283
e.status == ProbeStatus.sendFailed,284
),285
true,286
);287
expect(PingStats.of(events).lossPercent, 100);288
});289
});291
group('extended probes on this host (loopback)', () {292
test('UDP traceroute: the destination answers port unreachable', () async {293
for (final target in [294
InternetAddress.loopbackIPv4,295
InternetAddress.loopbackIPv6,296
]) {297
final events = await collect(298
IcmpProbe.traceroute(299
target,300
const TraceSettings(method: TraceMethod.udp, probes: 2),301
),302
);303
expect(events, hasLength(2), reason: '$target');304
expect(305
events.every(306
(e) =>307
e.ttl == 1 &&308
e.status == ProbeStatus.reply &&309
e.note == 'portUnreachable' &&310
e.from == target,311
),312
true,313
reason: '$target',314
);315
}316
});318
test(319
'TCP traceroute: open port (SYN-ACK) and closed port (reset)',320
() async {321
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);322
final port = server.port;323
final open = await collect(324
IcmpProbe.traceroute(325
InternetAddress.loopbackIPv4,326
TraceSettings(method: TraceMethod.tcp, port: port, probes: 2),327
),328
);329
expect(open.map((e) => e.note), ['tcpOpen', 'tcpOpen']);330
await server.close();331
final closed = await collect(332
IcmpProbe.traceroute(333
InternetAddress.loopbackIPv4,334
TraceSettings(method: TraceMethod.tcp, port: port, probes: 1),335
),336
);337
expect(closed.single.status, ProbeStatus.reply);338
expect(closed.single.note, 'tcpRefused');339
},340
skip: Posix.current?.darwin == true341
? false342
: 'TCP traceroute is Darwin only',343
);345
test('first TTL and probe interval are honoured', () async {346
final watch = Stopwatch()..start();347
final events = await collect(348
IcmpProbe.traceroute(349
InternetAddress.loopbackIPv4,350
const TraceSettings(351
firstTtl: 5,352
probes: 3,353
interval: Duration(milliseconds: 150),354
),355
),356
);357
expect(events.every((e) => e.ttl == 5), true);358
expect(watch.elapsedMilliseconds, greaterThanOrEqualTo(290));359
});361
test('ping with TTL, Don\'t Fragment and continuous mode', () async {362
final ttl = await collect(363
IcmpProbe.ping(364
InternetAddress.loopbackIPv4,365
const PingSettings(count: 2, ttl: 3, dontFragment: true),366
),367
);368
expect(ttl.every((e) => e.status == ProbeStatus.reply), true);369
expect(ttl.every((e) => e.time != null), true);370
// A payload above the loopback MTU cannot leave unfragmented.371
final big = await collect(372
IcmpProbe.ping(373
InternetAddress.loopbackIPv4,374
const PingSettings(count: 1, size: 20000, dontFragment: true),375
),376
);377
expect(big.single.status, ProbeStatus.sendFailed);378
expect(big.single.note, 'messageTooLong');380
final run = IcmpProbe.ping(381
InternetAddress.loopbackIPv4,382
const PingSettings(count: 0, interval: Duration(milliseconds: 200)),383
);384
final got = <ProbeEvent>[];385
final done = Completer<void>();386
run.events.listen(got.add, onDone: done.complete);387
await Future<void>.delayed(const Duration(milliseconds: 1500));388
await run.cancel();389
await done.future.timeout(const Duration(seconds: 2));390
expect(got.length, greaterThanOrEqualTo(4));391
expect(got.map((e) => e.seq).take(4), [1, 2, 3, 4]);392
}, skip: Posix.current?.darwin == true ? false : 'checked on device');393
});395
// Real Internet measurements; opt-in because they need network access.396
final internet = Platform.environment['SM_NET_TEST'] == '1';397
group('real ICMP over the Internet', () {398
for (final target in ['1.1.1.1', '2606:4700:4700::1111']) {399
test('ping and traceroute $target', () async {400
final address = InternetAddress(target);401
final ping = await collect(402
IcmpProbe.ping(address, const PingSettings(count: 3)),403
);404
expect(PingStats.of(ping).received, greaterThan(0));405
final trace = await collect(406
IcmpProbe.traceroute(address, const TraceSettings(maxHops: 30)),407
);408
final hops = trace.map((e) => e.ttl).toSet();409
expect(hops.length, greaterThan(1), reason: 'intermediate routers');410
expect(411
trace.any((e) => e.status == ProbeStatus.timeExceeded),412
true,413
reason: 'routers answered with Time Exceeded',414
);415
expect(416
trace417
.where((e) => e.status == ProbeStatus.reply)418
.every((e) => e.from == address),419
true,420
);421
for (final method in [TraceMethod.udp, TraceMethod.tcp]) {422
if (IcmpProbe.unsupported(method) != null) continue;423
final other = await collect(424
IcmpProbe.traceroute(425
address,426
TraceSettings(427
method: method,428
port: method == TraceMethod.tcp ? 443 : null,429
),430
),431
);432
expect(433
other.any((e) => e.status == ProbeStatus.timeExceeded),434
true,435
reason: '$method: routers answered',436
);437
expect(438
other.any((e) => e.status == ProbeStatus.reply),439
true,440
reason: '$method: destination reached',441
);442
}443
// ignore: avoid_print444
print(445
[446
for (final e in trace)447
'${e.ttl}.${e.seq} ${e.status.name} ${e.from?.address ?? '*'} '448
'${e.rttMs?.toStringAsFixed(1) ?? ''}',449
].join('\n'),450
);451
}, timeout: const Timeout(Duration(minutes: 5)));452
}453
}, skip: !internet);454
}SHA-256
4f53b964f9c7115f0a443f110114746a412915cb4a6fa9a93bfd3818a00690f4CH-J Proprietary Software License 1.14 · تُتاح الشيفرة المصدرية بموجب CH-J Proprietary Software License 1.14. وإتاحتها لا تغيّر شروط الترخيص ولا تمنح حقوقًا إضافية.
SHA-256 للأرشيف
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655