1use crate::LogError;
6use diagnostics_data::{ExtendedMoniker, LogsData};
7use diagnostics_log_encoding::{Header, LOG_CONTROL_BIT, MONIKER, URL, Value};
8use diagnostics_message::MonikerWithUrl;
9use flyweights::FlyStr;
10use futures_util::stream::FusedStream;
11use futures_util::{AsyncReadExt, stream};
12use std::collections::HashMap;
13use zerocopy::FromBytes;
14
15pub struct FxtStreamer {
16 socket_reader: SocketReader,
17 tags: HashMap<u32, MonikerWithUrl>,
18}
19
20impl FxtStreamer {
21 pub fn new(socket: flex_client::AsyncSocket) -> Self {
22 Self { socket_reader: SocketReader::new(socket), tags: HashMap::default() }
23 }
24
25 pub fn stream(self) -> impl FusedStream<Item = Result<LogsData, LogError>> {
26 stream::unfold(Some(self), |maybe_this| async {
27 let mut this = maybe_this?;
28 let item = this.next_message().await.transpose()?;
29 let maybe_this = item.is_ok().then_some(this);
30 Some((item, maybe_this))
31 })
32 }
33
34 async fn next_message(&mut self) -> Result<Option<LogsData>, LogError> {
35 loop {
36 let Some(header_bytes) = self.socket_reader.fill(8).await? else {
38 return Ok(None);
39 };
40
41 let header = Header::read_from_bytes(header_bytes)
43 .map_err(|_| LogError::UnknownError(anyhow::anyhow!("Failed to parse header")))?;
44 let record_len = header.size_words() as usize * 8;
45 if record_len < 8 {
46 Err(LogError::UnknownError(anyhow::anyhow!("Invalid record size in header")))?;
47 }
48
49 let record_bytes = self.socket_reader.fill(record_len).await?.unwrap();
51
52 let (record, remaining) = diagnostics_log_encoding::parse::parse_record(record_bytes)
54 .map_err(|e| LogError::UnknownError(e.into()))?;
55 debug_assert!(remaining.is_empty());
56
57 let tag = header.tag();
59 let is_manifest = (tag & LOG_CONTROL_BIT) != 0;
60 let actual_tag = tag & !LOG_CONTROL_BIT;
61
62 if is_manifest {
63 let mut moniker = None;
64 let mut url = None;
65 for arg in &record.arguments {
66 if arg.name() == MONIKER
67 && let Value::Text(t) = arg.value()
68 {
69 moniker = Some(
70 ExtendedMoniker::parse_str(&t)
71 .map_err(|e| LogError::UnknownError(e.into()))?,
72 );
73 } else if arg.name() == URL
74 && let Value::Text(t) = arg.value()
75 {
76 url = Some(FlyStr::new(t));
77 }
78 }
79 if let (Some(moniker), Some(url)) = (moniker, url) {
80 self.tags.insert(actual_tag, MonikerWithUrl { moniker, url });
81 }
82 self.socket_reader.consume(record_len);
83 continue;
84 }
85
86 let source = self.tags.get(&actual_tag).cloned().unwrap_or_else(|| MonikerWithUrl {
87 moniker: ExtendedMoniker::parse_str("/UNKNOWN").unwrap(),
88 url: FlyStr::new(""),
89 });
90 let logs_data = diagnostics_message::from_structured(source, record_bytes)
91 .map_err(|e| LogError::UnknownError(e.into()))?;
92
93 self.socket_reader.consume(record_len);
94 return Ok(Some(logs_data));
95 }
96 }
97}
98
99struct SocketReader {
100 socket: flex_client::AsyncSocket,
101 buffer: Vec<u8>,
102 head: usize,
103}
104
105impl SocketReader {
106 fn new(socket: flex_client::AsyncSocket) -> Self {
107 Self { socket, buffer: Vec::new(), head: 0 }
108 }
109
110 async fn fill(&mut self, count: usize) -> Result<Option<&[u8]>, LogError> {
113 let remaining = self.buffer.len() - self.head;
114 if remaining < count {
115 if self.head > 0 {
116 self.buffer.copy_within(self.head.., 0);
117 self.buffer.truncate(remaining);
118 self.head = 0;
119 }
120 while self.buffer.len() < count {
121 let start_len = self.buffer.len();
122 self.buffer.resize(std::cmp::max(start_len + 4096, count), 0);
123 let n = self
124 .socket
125 .read(&mut self.buffer[start_len..])
126 .await
127 .map_err(LogError::IOError)?;
128 self.buffer.truncate(start_len + n);
129 if n == 0 {
130 if !self.buffer.is_empty() {
131 return Err(LogError::UnknownError(anyhow::anyhow!(
132 "Truncated record at end of stream"
133 )));
134 }
135 return Ok(None);
136 }
137 }
138 }
139 Ok(Some(&self.buffer[self.head..self.head + count]))
140 }
141
142 fn consume(&mut self, count: usize) {
144 self.head += count;
145 if self.head == self.buffer.len() {
146 self.buffer.clear();
147 self.head = 0;
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use assert_matches::assert_matches;
156 use diagnostics_log_encoding::encode::{Encoder, EncoderOpts, ResizableBuffer};
157 use diagnostics_log_encoding::{Argument, Record};
158 use fuchsia_async as fasync;
159 use futures_util::StreamExt;
160 use zerocopy::IntoBytes;
161
162 fn fn_encode(record: Record<'_>, tag: u32) -> Vec<u8> {
163 let mut encoder = Encoder::new(
164 std::io::Cursor::new(ResizableBuffer::from(Vec::new())),
165 EncoderOpts::default(),
166 );
167 encoder.write_record(record).unwrap();
168 let mut bytes = encoder.take().into_inner().into_inner();
169 let mut header = Header::read_from_bytes(&bytes[0..8]).unwrap();
170 header.set_tag(tag);
171 bytes[0..8].copy_from_slice(header.as_bytes());
172 bytes
173 }
174
175 #[fuchsia::test]
177 async fn test_socket_reader_clean_eof() {
178 let (sender, receiver) = zx::Socket::create_stream();
179 let mut reader = SocketReader::new(flex_client::socket_to_async(receiver));
180 drop(sender);
181 assert!(reader.fill(8).await.unwrap().is_none());
182 }
183
184 #[fuchsia::test]
185 async fn test_socket_reader_truncated() {
186 let (sender, receiver) = zx::Socket::create_stream();
187 let mut reader = SocketReader::new(flex_client::socket_to_async(receiver));
188 sender.write(b"1234").unwrap();
189 drop(sender);
190 assert_matches!(
191 reader.fill(8).await,
192 Err(LogError::UnknownError(e))
193 if e.to_string().contains("Truncated record at end of stream")
194 );
195 }
196
197 #[fuchsia::test]
198 async fn test_socket_reader_shift_and_consume() {
199 let (sender, receiver) = zx::Socket::create_stream();
200 let mut reader = SocketReader::new(flex_client::socket_to_async(receiver));
201
202 sender.write(b"abcdefghijklmnop").unwrap();
204 drop(sender);
205
206 let slice1 = reader.fill(8).await.unwrap().unwrap();
208 assert_eq!(slice1, b"abcdefgh");
209 reader.consume(4); let slice2 = reader.fill(8).await.unwrap().unwrap();
214 assert_eq!(slice2, b"efghijkl");
215 reader.consume(6); assert_matches!(
221 reader.fill(8).await,
222 Err(LogError::UnknownError(e))
223 if e.to_string().contains("Truncated record at end of stream") => {}
224 );
225 }
226
227 #[fuchsia::test]
228 async fn test_socket_reader_partial_reads_loop() {
229 let (sender, receiver) = zx::Socket::create_stream();
230 let mut reader = SocketReader::new(flex_client::socket_to_async(receiver));
231
232 sender.write(b"1234").unwrap();
234
235 let push_task = fasync::Task::spawn(async move {
237 fasync::Timer::new(std::time::Duration::from_millis(10)).await;
238 sender.write(b"5678").unwrap();
239 });
240
241 let slice = reader.fill(8).await.unwrap().unwrap();
242 assert_eq!(slice, b"12345678");
243 push_task.await;
244 }
245
246 #[fuchsia::test]
248 async fn test_fxt_streamer_invalid_record_size() {
249 let (sender, receiver) = zx::Socket::create_stream();
250 let streamer = FxtStreamer::new(flex_client::socket_to_async(receiver));
251 let mut stream = std::pin::pin!(streamer.stream());
252
253 sender.write(&[0; 8]).unwrap();
255 drop(sender);
256
257 assert_matches!(
258 stream.next().await,
259 Some(Err(LogError::UnknownError(e)))
260 if e.to_string().contains("Invalid record size in header")
261 );
262 }
263
264 #[fuchsia::test]
265 async fn test_fxt_streamer_truncated_record() {
266 let (sender, receiver) = zx::Socket::create_stream();
267 let streamer = FxtStreamer::new(flex_client::socket_to_async(receiver));
268 let mut stream = std::pin::pin!(streamer.stream());
269
270 let valid_16_byte_record = fn_encode(
272 Record {
273 timestamp: zx::BootInstant::from_nanos(0),
274 severity: 0x30,
275 arguments: vec![Argument::message("1234")],
276 },
277 1,
278 );
279
280 sender.write(&valid_16_byte_record[0..8]).unwrap();
282 drop(sender);
283
284 assert_matches!(
285 stream.next().await,
286 Some(Err(LogError::UnknownError(e)))
287 if e.to_string().contains("Truncated record at end of stream")
288 );
289 }
290
291 #[fuchsia::test]
292 async fn test_fxt_streamer_parse_error() {
293 let (sender, receiver) = zx::Socket::create_stream();
294 let streamer = FxtStreamer::new(flex_client::socket_to_async(receiver));
295 let mut stream = std::pin::pin!(streamer.stream());
296
297 let valid_16_byte_record = fn_encode(
299 Record {
300 timestamp: zx::BootInstant::from_nanos(0),
301 severity: 0x30,
302 arguments: vec![Argument::message("1234")],
303 },
304 1,
305 );
306
307 sender.write(&valid_16_byte_record[0..8]).unwrap();
309 sender.write(&[0xff; 8]).unwrap(); drop(sender);
311
312 assert_matches!(stream.next().await, Some(Err(LogError::UnknownError(_))));
313 }
314
315 #[fuchsia::test]
317 async fn test_fxt_streamer_unknown_tag() {
318 let (sender, receiver) = zx::Socket::create_stream();
319 let streamer = FxtStreamer::new(flex_client::socket_to_async(receiver));
320 let mut stream = std::pin::pin!(streamer.stream());
321
322 let log_bytes = fn_encode(
323 Record {
324 timestamp: zx::BootInstant::from_nanos(123),
325 severity: 0x30,
326 arguments: vec![Argument::message("hello")],
327 },
328 1,
329 );
330 sender.write(&log_bytes).unwrap();
331 drop(sender);
332
333 let item = stream.next().await.unwrap().unwrap();
334 assert_eq!(item.moniker.to_string(), "UNKNOWN");
335 assert_eq!(item.metadata.component_url, Some(FlyStr::new("")));
336 assert_eq!(item.msg().unwrap(), "hello");
337 }
338
339 #[fuchsia::test]
340 async fn test_fxt_streamer_partial_manifest_ignored() {
341 let (sender, receiver) = zx::Socket::create_stream();
342 let streamer = FxtStreamer::new(flex_client::socket_to_async(receiver));
343 let mut stream = std::pin::pin!(streamer.stream());
344
345 let manifest_bytes = fn_encode(
347 Record {
348 timestamp: zx::BootInstant::from_nanos(0),
349 severity: 0x30,
350 arguments: vec![Argument::other(MONIKER, "core/partial")],
351 },
352 1 | LOG_CONTROL_BIT,
353 );
354
355 let log_bytes = fn_encode(
356 Record {
357 timestamp: zx::BootInstant::from_nanos(123),
358 severity: 0x30,
359 arguments: vec![Argument::message("hello")],
360 },
361 1,
362 );
363
364 sender.write(&manifest_bytes).unwrap();
365 sender.write(&log_bytes).unwrap();
366 drop(sender);
367
368 let item = stream.next().await.unwrap().unwrap();
370 assert_eq!(item.moniker.to_string(), "UNKNOWN");
371 assert_eq!(item.metadata.component_url, Some(FlyStr::new("")));
372 assert_eq!(item.msg().unwrap(), "hello");
373 }
374
375 #[fuchsia::test]
376 async fn test_fxt_streamer_multiple_messages() {
377 let (sender, receiver) = zx::Socket::create_stream();
378 let streamer = FxtStreamer::new(flex_client::socket_to_async(receiver));
379 let mut stream = std::pin::pin!(streamer.stream());
380
381 let manifest_a = fn_encode(
382 Record {
383 timestamp: zx::BootInstant::from_nanos(0),
384 severity: 0x30,
385 arguments: vec![Argument::other(MONIKER, "core/a"), Argument::other(URL, "url_a")],
386 },
387 1 | LOG_CONTROL_BIT,
388 );
389
390 let log_a1 = fn_encode(
391 Record {
392 timestamp: zx::BootInstant::from_nanos(1),
393 severity: 0x30,
394 arguments: vec![Argument::message("msg a1")],
395 },
396 1,
397 );
398
399 let log_a2 = fn_encode(
400 Record {
401 timestamp: zx::BootInstant::from_nanos(2),
402 severity: 0x30,
403 arguments: vec![Argument::message("msg a2")],
404 },
405 1,
406 );
407
408 let log_unknown = fn_encode(
409 Record {
410 timestamp: zx::BootInstant::from_nanos(3),
411 severity: 0x30,
412 arguments: vec![Argument::message("msg unknown")],
413 },
414 99, );
416
417 sender.write(&manifest_a).unwrap();
418 sender.write(&log_a1).unwrap();
419 sender.write(&log_a2).unwrap();
420 sender.write(&log_unknown).unwrap();
421 drop(sender);
422
423 let item1 = stream.next().await.unwrap().unwrap();
424 assert_eq!(item1.moniker.to_string(), "core/a");
425 assert_eq!(item1.msg().unwrap(), "msg a1");
426
427 let item2 = stream.next().await.unwrap().unwrap();
428 assert_eq!(item2.moniker.to_string(), "core/a");
429 assert_eq!(item2.msg().unwrap(), "msg a2");
430
431 let item3 = stream.next().await.unwrap().unwrap();
432 assert_eq!(item3.moniker.to_string(), "UNKNOWN");
433 assert_eq!(item3.msg().unwrap(), "msg unknown");
434
435 assert!(stream.next().await.is_none());
436 }
437}