1"""Verify successful I/O inside overlapping SMB reader and writer guards."""
2import json
3from collections import Counter
4
5
6class PendingOverlap(AssertionError):
7 pass
8
9
10def renames(request):
11 return request['command'] == 17 and request.get('info_type') == 1 and request.get('info_class') == 10
12
13
14def verify(events, phase=None):
15 pending, files, peers, pairs = {}, {}, {}, {}
16 progress = Counter()
17 path_epoch = 0
18 active_since = -1 if phase is None else None
19 for index, event in enumerate(events):
20 assert not event.get('trace_error'), 'Trace collection failed'
21 assert not event.get('encrypted'), 'Encrypted traffic cannot establish I/O overlap'
22 if phase is not None and event.get('control', {}).get('phase') == phase:
23 active_since = index
24 connection = event.get('connection')
25 if event.get('opened'):
26 peers[connection] = event['peer'][0]
27 if event.get('closed'):
28 files = {key: value for key, value in files.items() if key[0] != connection}
29 if 'command' not in event:
30 continue
31 message = connection, event['message']
32 if event['direction'] == 'request':
33 if renames(event):
34 files.clear()
35 path_epoch += 1
36 pending[message] = index, event, path_epoch
37 if event['command'] == 6:
38 files.pop((connection, event['file_id']), None)
39 elif event['command'] == 10:
40 file = files.get((connection, event['file_id']))
41 if file:
42 for offset, length, flags in event['locks']:
43 if flags & 4:
44 file['locks'] = {at: lock for at, lock in file['locks'].items()
45 if not offset <= at < offset + length}
46 continue
47 if event['status'] == '0x103':
48 continue
49 if message not in pending and event['command'] in (0, 18):
50 continue
51 request_index, request, request_epoch = pending.pop(message)
52 if renames(request):
53 files.clear()
54 path_epoch += 1
55 if event['status'] != '0x0':
56 continue
57 command = event['command']
58 if command == 5:
59 if request_epoch != path_epoch or any(renames(request) for _, request, _ in pending.values()):
60 continue
61 files[connection, event['file_id']] = {
62 'path': request['path'].lower(), 'access': request['access'],
63 'locks': {},
64 }
65 continue
66 if command not in (8, 9, 10):
67 continue
68 key = connection, request['file_id']
69 if key not in files:
70 continue
71 file = files[key]
72 if command == 10:
73 for offset, length, flags in request['locks']:
74 if length != 1 or offset not in (0xfffffffb, 0xfffffffd):
75 continue
76 if flags & 4:
77 file['locks'].pop(offset, None)
78 else:
79 file['locks'][offset] = index, flags & 3
80 continue
81 if active_since is None or request_index <= active_since or not file['path'].endswith('synthetic.one') or request['length'] == 0:
82 continue
83 native = peers[connection].startswith('192.168.77.')
84 progress[('native' if native else 'host', connection, 'read' if command == 8 else 'write')] += 1
85 for other_key, other in files.items():
86 if key[0] == other_key[0] or file['path'] != other['path']:
87 continue
88 reader, writer = (file, other) if command == 8 else (other, file)
89 reader_key, writer_key = (key, other_key) if command == 8 else (other_key, key)
90 reader_lock = reader['locks'].get(0xfffffffb)
91 writer_lock = writer['locks'].get(0xfffffffd)
92 # Only read-only handles distinguish a reader from a writer's own validation reads.
93 if reader['access'] & 0x40000002 or not reader_lock or not writer_lock:
94 continue
95 if reader_lock[1] != 1 or writer_lock[1] != 2:
96 continue
97 if request_index <= max(reader_lock[0], writer_lock[0]):
98 continue
99 pair = reader_key + (reader_lock[0],) + writer_key + (writer_lock[0],)
100 observed = pairs.setdefault(pair, {'read': 0, 'write': 0,
101 'reader_peer': peers[reader_key[0]], 'writer_peer': peers[writer_key[0]]})
102 observed['read' if command == 8 else 'write'] += 1
103 assert active_since is not None, 'Requested trace phase was not observed'
104 both = [pair for pair in pairs.values() if pair['read'] and pair['write']]
105 native_writer_pairs = [pair for pair in both if pair['writer_peer'].startswith('192.168.77.')
106 and not pair['reader_peer'].startswith('192.168.77.')]
107 rust_writer_pairs = [pair for pair in both if not pair['writer_peer'].startswith('192.168.77.')]
108 if not both: raise PendingOverlap('No reader/writer guard pair performed both successful reads and writes while overlapping')
109 if not native_writer_pairs: raise PendingOverlap('No active Rust reader overlapped successful native writes')
110 if not rust_writer_pairs: raise PendingOverlap('No active reader overlapped successful Rust writes')
111 return {'active_guard_pairs': len(both), 'active_native_writer_pairs': len(native_writer_pairs),
112 'active_rust_writer_pairs': len(rust_writer_pairs),
113 'overlapping_reads': sum(pair['read'] for pair in both),
114 'overlapping_writes': sum(pair['write'] for pair in both),
115 'connections': [{'kind': kind, 'connection': connection, 'operation': operation, 'count': count}
116 for (kind, connection, operation), count in sorted(progress.items())]}
117
118
119if __name__ == '__main__':
120 import sys
121 with open(sys.argv[1]) as stream:
122 result = verify(json.loads(line) for line in stream)
123 print(json.dumps(result, indent=2))