-
Notifications
You must be signed in to change notification settings - Fork 15
/
wsinspect.rs
146 lines (120 loc) · 4.06 KB
/
wsinspect.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#![warn(clippy::pedantic)]
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::{i64, io, result};
use bytes::{Buf, BytesMut};
use structopt::StructOpt;
use tokio_util::codec::Decoder;
use websocket_codec::protocol::{DataLength, FrameHeader, FrameHeaderCodec};
use websocket_codec::{Opcode, Result};
fn decode_stream<S: BufRead, C: Decoder>(codec: &mut C, mut stream: S) -> result::Result<Option<C::Item>, C::Error> {
let mut prev_buf_len = 0;
loop {
let buf = stream.fill_buf()?;
if buf.len() == prev_buf_len {
return Ok(None);
}
prev_buf_len = buf.len();
let mut read_buf = BytesMut::from(buf);
let prev_remaining = read_buf.remaining();
let result = codec.decode(&mut read_buf);
let consumed = prev_remaining - read_buf.remaining();
stream.consume(consumed);
if let Some(frame) = result? {
return Ok(Some(frame));
}
}
}
#[allow(clippy::cast_possible_wrap)]
fn seek_forward<S: Seek>(mut stream: S, bytes: u64) -> result::Result<u64, io::Error> {
let delta = bytes as i64;
let delta = if delta < 0 {
stream.seek(SeekFrom::Current(i64::MAX))?;
let rest = (bytes - i64::MAX as u64) as i64;
assert!(rest >= 0);
rest
} else {
delta
};
stream.seek(SeekFrom::Current(delta))
}
fn display(header: &FrameHeader) -> String {
let opcode = header.opcode();
let opcode = Opcode::try_from(opcode).map_or_else(|| opcode.to_string(), |opcode| format!("{:?}", opcode));
let mask = header
.mask()
.map(|mask| format!(", mask: 0x{:x}", u32::from(mask)))
.unwrap_or_default();
format!(
"{{ fin: {}, rsv: {}, opcode: {}{}, data_len: {:?} }}",
header.fin(),
header.rsv(),
opcode,
mask,
header.data_len()
)
}
fn inspect(path: &Path, dump_header: bool, dump_data: bool) -> Result<()> {
let mut stream = BufReader::new(File::open(path)?);
let file_len = stream.seek(SeekFrom::End(0))?;
stream.seek(SeekFrom::Start(0))?;
while let Some(header) = decode_stream(&mut FrameHeaderCodec, &mut stream)? {
if dump_header {
println!("{}: {}", path.to_string_lossy(), display(&header));
}
let data_len: u64 = match header.data_len() {
DataLength::Small(n) => n.into(),
DataLength::Medium(n) => n.into(),
DataLength::Large(n) => n,
};
let actual_data_len = if dump_data {
let mut stream = stream.by_ref().take(data_len);
let stdout = io::stdout();
let mut stdout = stdout.lock();
io::copy(&mut stream, &mut stdout)?
} else {
let prev_pos = stream.seek(SeekFrom::Current(0))?;
let pos = seek_forward(&mut stream, data_len)
.map(|pos| pos.min(file_len))
.unwrap_or(file_len);
pos - prev_pos
};
if actual_data_len != data_len {
return Err(format!(
"stream contains incomplete data: expected {0} bytes (0x{0:x} bytes), got {1} bytes (0x{1:x} bytes)",
data_len, actual_data_len
)
.into());
}
}
let buf = stream.fill_buf()?;
if !buf.is_empty() {
return Err(format!("additional {} data bytes at end of stream: {:?}", buf.len(), buf).into());
}
Ok(())
}
#[derive(Debug, StructOpt)]
#[structopt(name = "wsinspect", about = "Inspect WebSocket protocol data")]
struct Opt {
/// Disables display of frame headers
#[structopt(long)]
no_dump_header: bool,
/// Displays frame payload data
#[structopt(long)]
dump_data: bool,
#[structopt(parse(from_os_str))]
files: Vec<PathBuf>,
}
fn main() {
let Opt {
files,
no_dump_header,
dump_data,
} = Opt::from_args();
for path in files {
if let Err(e) = inspect(&path, !no_dump_header, dump_data) {
eprintln!("{}: {}", path.to_string_lossy(), e);
}
}
}