{"record":{"id":"a8ba57576ac9c7f5","repo":"n0-computer/iroh","slug":"emptypacket","errorCode":"EmptyPacket","errorMessage":"Attempted to send empty packet","messagePattern":"Attempted to send empty packet","errorType":"validation","errorClass":"SendError","httpStatus":null,"severity":"error","filePath":"iroh-relay/src/client/conn.rs","lineNumber":154,"sourceCode":"        }\n    }\n}\n\nimpl Sink<ClientToRelayMsg> for Conn {\n    type Error = SendError;\n\n    fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Pin::new(&mut self.conn).poll_ready(cx).map_err(Into::into)\n    }\n\n    fn start_send(mut self: Pin<&mut Self>, frame: ClientToRelayMsg) -> Result<(), Self::Error> {\n        let size = frame.encoded_len();\n        ensure!(\n            size <= MAX_PACKET_SIZE,\n            SendError::ExceedsMaxPacketSize { size }\n        );\n        if let ClientToRelayMsg::Datagrams { datagrams, .. } = &frame {\n            ensure!(!datagrams.contents.is_empty(), SendError::EmptyPacket);\n        }\n\n        Pin::new(&mut self.conn)\n            .start_send(frame.to_bytes().freeze())\n            .map_err(Into::into)\n    }\n\n    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Pin::new(&mut self.conn).poll_flush(cx).map_err(Into::into)\n    }\n\n    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Pin::new(&mut self.conn).poll_close(cx).map_err(Into::into)\n    }\n}\n","sourceCodeStart":136,"sourceCodeEnd":170,"githubUrl":"https://github.com/n0-computer/iroh/blob/2b4de030ce5e0133f272871a76f0c685c63f552a/iroh-relay/src/client/conn.rs#L136-L170","documentation":"SendError::EmptyPacket is raised in start_send when a ClientToRelayMsg::Datagrams frame carries an empty datagram list. Sending an empty packet would waste a round trip and violates the relay protocol, so it is rejected before encoding. Note the size check happens first, so an empty batch must still be non-empty to pass.","triggerScenarios":"Sending a Datagrams message after its contents were fully drained/flushed, or constructing ClientToRelayMsg::Datagrams with datagrams.contents == vec![] and sending it on the connection.","commonSituations":"Queue-based senders that pop datagrams into a batch, then flush an empty batch when the queue was momentarily empty; race between batch assembly and connection flush.","solutions":["Skip the send entirely when datagrams.contents.is_empty() before constructing/sending the frame.","Only build a Datagrams batch once at least one datagram has been queued.","Audit flush logic so empty batches are dropped instead of sent."],"exampleFix":"// before\nlet msg = ClientToRelayMsg::Datagrams { datagrams, .. };\nclient.send(msg).await?;\n\n// after\nif !datagrams.contents.is_empty() {\n    client.send(ClientToRelayMsg::Datagrams { datagrams, .. }).await?;\n}","handlingStrategy":"validation","validationCode":"if datagrams.contents.is_empty() {\n    return; // skip send\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only construct Datagrams batches when at least one datagram is queued.","Make batch-assembly helpers return Option and skip sending on None.","In flush loops, check for an empty queue before building a frame."],"tags":["network","relay","empty-packet","iroh"],"backgroundTag":"empty-required-field","analyzedSha":"2b4de030ce5e0133f272871a76f0c685c63f552a","analyzedAt":"2026-09-08T04:26:47.755Z","contentChangedAt":"2026-09-08T04:26:47.755Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}