Skip to main content

scylla_proxy/
frame.rs

1use std::collections::HashMap;
2
3use bytes::{Buf, BufMut, Bytes, BytesMut};
4use scylla_cql::frame::flag;
5use scylla_cql::frame::frame_errors::FrameHeaderParseError;
6use scylla_cql::frame::protocol_features::ProtocolFeatures;
7pub use scylla_cql::frame::request::RequestOpcode;
8use scylla_cql::frame::request::{RequestDeserializationError, RequestV2};
9pub use scylla_cql::frame::response::ResponseOpcode;
10use scylla_cql::frame::response::error::DbError;
11use scylla_cql::frame::types;
12use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
13
14use tracing::warn;
15
16use crate::errors::ReadFrameError;
17use crate::proxy::CompressionReader;
18
19const HEADER_SIZE: usize = 9;
20
21// Parts of the frame header which are not determined by the request/response type.
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
23pub struct FrameParams {
24    pub version: u8,
25    pub flags: u8,
26    pub stream: i16,
27}
28
29impl FrameParams {
30    pub const fn for_request(&self) -> FrameParams {
31        Self {
32            version: self.version & 0x7F,
33            ..*self
34        }
35    }
36    pub const fn for_response(&self) -> FrameParams {
37        Self {
38            version: 0x80 | (self.version & 0x7F),
39            ..*self
40        }
41    }
42
43    /// Tells whether the frame carried a compressed body on the wire.
44    ///
45    /// Note that frames handed out by the proxy always expose a *decompressed*
46    /// body, so this flag (together with [`RequestFrame::wire_body_len`]) is the
47    /// only way to tell that compression was in play.
48    pub const fn is_compressed(&self) -> bool {
49        self.flags & flag::COMPRESSION != 0
50    }
51}
52
53#[derive(Copy, Clone, Debug)]
54pub(crate) enum FrameType {
55    Request,
56    Response,
57}
58
59#[derive(Copy, Clone, Debug, PartialEq, Eq)]
60pub(crate) enum FrameOpcode {
61    Request(RequestOpcode),
62    Response(ResponseOpcode),
63}
64
65#[derive(Clone, Debug, Eq)]
66pub struct RequestFrame {
67    pub params: FrameParams,
68    pub opcode: RequestOpcode,
69    /// The frame body, decompressed if the frame arrived compressed.
70    pub body: Bytes,
71    /// Number of body bytes as they appeared on the wire, i.e. *before* decompression.
72    /// For frames that were not compressed this is equal to `body.len()`.
73    ///
74    /// This is what makes it possible to observe how effective compression is;
75    /// [`Self::body`] is always decompressed, so its length says nothing about
76    /// how much data actually travelled over the network.
77    pub wire_body_len: usize,
78}
79
80/// Excludes [`RequestFrame::wire_body_len`], which is an observation about how the frame
81/// was transferred rather than a part of its logical content.
82impl PartialEq for RequestFrame {
83    fn eq(&self, other: &Self) -> bool {
84        self.params == other.params && self.opcode == other.opcode && self.body == other.body
85    }
86}
87
88impl RequestFrame {
89    /// Creates a frame whose body did not undergo compression, so that its
90    /// [`Self::wire_body_len`] is simply the body length.
91    pub fn new(params: FrameParams, opcode: RequestOpcode, body: Bytes) -> Self {
92        Self {
93            params,
94            opcode,
95            wire_body_len: body.len(),
96            body,
97        }
98    }
99
100    pub(crate) async fn write(
101        &self,
102        writer: &mut (impl AsyncWrite + Unpin),
103        compression: &CompressionReader,
104    ) -> Result<(), tokio::io::Error> {
105        write_frame(
106            self.params,
107            FrameOpcode::Request(self.opcode),
108            &self.body,
109            writer,
110            compression,
111        )
112        .await
113    }
114
115    pub fn deserialize(
116        &self,
117        features: &ProtocolFeatures,
118    ) -> Result<RequestV2<'_>, RequestDeserializationError> {
119        RequestV2::deserialize(&mut &self.body[..], self.opcode, features)
120    }
121}
122#[derive(Clone, Debug, Eq)]
123pub struct ResponseFrame {
124    pub params: FrameParams,
125    pub opcode: ResponseOpcode,
126    /// The frame body, decompressed if the frame arrived compressed.
127    pub body: Bytes,
128    /// Number of body bytes as they appeared on the wire, i.e. *before* decompression.
129    /// For frames that were not compressed this is equal to `body.len()`.
130    ///
131    /// See [`RequestFrame::wire_body_len`] for the rationale.
132    pub wire_body_len: usize,
133}
134
135/// Excludes [`ResponseFrame::wire_body_len`], which is an observation about how the frame
136/// was transferred rather than a part of its logical content.
137impl PartialEq for ResponseFrame {
138    fn eq(&self, other: &Self) -> bool {
139        self.params == other.params && self.opcode == other.opcode && self.body == other.body
140    }
141}
142
143impl ResponseFrame {
144    /// Creates a frame whose body did not undergo compression, so that its
145    /// [`Self::wire_body_len`] is simply the body length.
146    pub fn new(params: FrameParams, opcode: ResponseOpcode, body: Bytes) -> Self {
147        Self {
148            params,
149            opcode,
150            wire_body_len: body.len(),
151            body,
152        }
153    }
154
155    /// Creates a response frame that signifies the given DbError type.
156    /// Useful for testing server-side error handling in drivers.
157    pub fn forged_error(
158        request_params: FrameParams,
159        error: DbError,
160        msg: Option<&str>,
161    ) -> Result<Self, std::num::TryFromIntError> {
162        let msg = msg.unwrap_or("Proxy-triggered error.");
163        let len_bytes = (msg.len() as u16).to_be_bytes(); // string len is a short in CQL protocol
164        let code_bytes = error.code(&ProtocolFeatures::default()).to_be_bytes(); // TODO: configurable features
165        let body_len = msg.len() + code_bytes.len() + len_bytes.len();
166        let mut buf = BytesMut::with_capacity(body_len);
167
168        buf.extend_from_slice(&code_bytes);
169        buf.extend_from_slice(&len_bytes);
170        buf.extend_from_slice(msg.as_bytes());
171
172        serialize_error_specific_fields(&mut buf, error)?;
173
174        Ok(ResponseFrame::new(
175            request_params.for_response(),
176            ResponseOpcode::Error,
177            buf.freeze(),
178        ))
179    }
180
181    /// Creates a Supported response frame with given supported options.
182    pub fn forged_supported(
183        request_params: FrameParams,
184        options: &HashMap<String, Vec<String>>,
185    ) -> Result<Self, std::num::TryFromIntError> {
186        let mut buf = BytesMut::new();
187        types::write_string_multimap(options, &mut buf)?;
188
189        Ok(ResponseFrame::new(
190            request_params.for_response(),
191            ResponseOpcode::Supported,
192            buf.freeze(),
193        ))
194    }
195
196    pub fn forged_ready(request_params: FrameParams) -> Self {
197        ResponseFrame::new(
198            request_params.for_response(),
199            ResponseOpcode::Ready,
200            Bytes::new(),
201        )
202    }
203
204    pub(crate) async fn write(
205        &self,
206        writer: &mut (impl AsyncWrite + Unpin),
207        compression: &CompressionReader,
208    ) -> Result<(), tokio::io::Error> {
209        write_frame(
210            self.params,
211            FrameOpcode::Response(self.opcode),
212            &self.body,
213            writer,
214            compression,
215        )
216        .await
217    }
218}
219
220fn serialize_error_specific_fields(
221    buf: &mut BytesMut,
222    error: DbError,
223) -> Result<(), std::num::TryFromIntError> {
224    match error {
225        DbError::Unavailable {
226            consistency,
227            required,
228            alive,
229        } => {
230            types::write_consistency(consistency, buf);
231            types::write_int(required, buf);
232            types::write_int(alive, buf);
233        }
234        DbError::WriteTimeout {
235            consistency,
236            received,
237            required,
238            write_type,
239        } => {
240            types::write_consistency(consistency, buf);
241            types::write_int(received, buf);
242            types::write_int(required, buf);
243            types::write_string(write_type.as_str(), buf)?;
244        }
245        DbError::ReadTimeout {
246            consistency,
247            received,
248            required,
249            data_present,
250        } => {
251            types::write_consistency(consistency, buf);
252            types::write_int(received, buf);
253            types::write_int(required, buf);
254            buf.put_u8(u8::from(data_present));
255        }
256        DbError::ReadFailure {
257            consistency,
258            received,
259            required,
260            numfailures,
261            data_present,
262        } => {
263            types::write_consistency(consistency, buf);
264            types::write_int(received, buf);
265            types::write_int(required, buf);
266            types::write_int(numfailures, buf);
267            buf.put_u8(u8::from(data_present));
268        }
269        DbError::WriteFailure {
270            consistency,
271            received,
272            required,
273            numfailures,
274            write_type,
275        } => {
276            types::write_consistency(consistency, buf);
277            types::write_int(received, buf);
278            types::write_int(required, buf);
279            types::write_int(numfailures, buf);
280            types::write_string(write_type.as_str(), buf)?;
281        }
282        DbError::FunctionFailure {
283            keyspace,
284            function,
285            arg_types,
286        } => {
287            types::write_string(keyspace.as_str(), buf)?;
288            types::write_string(function.as_str(), buf)?;
289            types::write_string_list(&arg_types, buf)?;
290        }
291        DbError::AlreadyExists { keyspace, table } => {
292            types::write_string(keyspace.as_str(), buf)?;
293            types::write_string(table.as_str(), buf)?;
294        }
295        DbError::Unprepared { statement_id } => {
296            types::write_short_bytes(statement_id.as_ref(), buf)?;
297        }
298        _ => (),
299    }
300    Ok(())
301}
302
303pub(crate) async fn write_frame(
304    params: FrameParams,
305    opcode: FrameOpcode,
306    body: &[u8],
307    writer: &mut (impl AsyncWrite + Unpin),
308    compression: &CompressionReader,
309) -> Result<(), tokio::io::Error> {
310    let compressed_body = compression
311        .maybe_compress_body(params.flags, body)
312        .map_err(tokio::io::Error::other)?;
313
314    let body = compressed_body.as_deref().unwrap_or(body);
315
316    let mut header = [0; HEADER_SIZE];
317
318    header[0] = params.version;
319    header[1] = params.flags;
320    header[2..=3].copy_from_slice(&params.stream.to_be_bytes());
321    header[4] = match opcode {
322        FrameOpcode::Request(op) => op as u8,
323        FrameOpcode::Response(op) => op as u8,
324    };
325    header[5..9].copy_from_slice(&(body.len() as u32).to_be_bytes());
326
327    writer.write_all(&header).await?;
328    writer.write_all(body).await?;
329    writer.flush().await?;
330    Ok(())
331}
332
333/// Reads a single frame off the wire.
334///
335/// Returns the body already decompressed, accompanied by the number of body bytes
336/// that were actually read from the socket (i.e. the compressed length, if the frame
337/// was compressed).
338pub(crate) async fn read_frame(
339    reader: &mut (impl AsyncRead + Unpin),
340    frame_type: FrameType,
341    compression: &CompressionReader,
342) -> Result<(FrameParams, FrameOpcode, Bytes, usize), ReadFrameError> {
343    let mut raw_header = [0u8; HEADER_SIZE];
344    reader
345        .read_exact(&mut raw_header[..])
346        .await
347        .map_err(FrameHeaderParseError::HeaderIoError)?;
348
349    let mut buf = &raw_header[..];
350
351    let version = buf.get_u8();
352    {
353        let (err, valid_direction, direction_str) = match frame_type {
354            FrameType::Request => (FrameHeaderParseError::FrameFromServer, 0x00, "request"),
355            FrameType::Response => (FrameHeaderParseError::FrameFromClient, 0x80, "response"),
356        };
357        if version & 0x80 != valid_direction {
358            return Err(err.into());
359        }
360        let protocol_version = version & 0x7F;
361        if protocol_version != 0x04 {
362            warn!(
363                "Received {} with protocol version {}.",
364                direction_str, protocol_version
365            );
366        }
367    }
368
369    let flags = buf.get_u8();
370    let stream = buf.get_i16();
371
372    let frame_params = FrameParams {
373        version,
374        flags,
375        stream,
376    };
377
378    let opcode = match frame_type {
379        FrameType::Request => FrameOpcode::Request(
380            RequestOpcode::try_from(buf.get_u8())
381                .map_err(|_| FrameHeaderParseError::FrameFromServer)?,
382        ),
383        FrameType::Response => FrameOpcode::Response(
384            ResponseOpcode::try_from(buf.get_u8())
385                .map_err(|_| FrameHeaderParseError::FrameFromClient)?,
386        ),
387    };
388
389    let length = buf.get_u32() as usize;
390
391    let mut body = Vec::with_capacity(length).limit(length);
392
393    while body.has_remaining_mut() {
394        let n = reader
395            .read_buf(&mut body)
396            .await
397            .map_err(|err| FrameHeaderParseError::BodyChunkIoError(body.remaining_mut(), err))?;
398        if n == 0 {
399            // EOF, too early
400            return Err(
401                FrameHeaderParseError::ConnectionClosed(body.remaining_mut(), length).into(),
402            );
403        }
404    }
405
406    let body = compression.maybe_decompress_body(flags, body.into_inner().into())?;
407
408    // `length` is the number of body bytes read off the socket, before any decompression.
409    Ok((frame_params, opcode, body, length))
410}
411
412pub(crate) async fn read_request_frame(
413    reader: &mut (impl AsyncRead + Unpin),
414    compression: &CompressionReader,
415) -> Result<RequestFrame, ReadFrameError> {
416    read_frame(reader, FrameType::Request, compression)
417        .await
418        .map(|(params, opcode, body, wire_body_len)| RequestFrame {
419            params,
420            opcode: match opcode {
421                FrameOpcode::Request(op) => op,
422                FrameOpcode::Response(_) => unreachable!(),
423            },
424            body,
425            wire_body_len,
426        })
427}
428
429pub(crate) async fn read_response_frame(
430    reader: &mut (impl AsyncRead + Unpin),
431    compression: &CompressionReader,
432) -> Result<ResponseFrame, ReadFrameError> {
433    read_frame(reader, FrameType::Response, compression)
434        .await
435        .map(|(params, opcode, body, wire_body_len)| ResponseFrame {
436            params,
437            opcode: match opcode {
438                FrameOpcode::Request(_) => unreachable!(),
439                FrameOpcode::Response(op) => op,
440            },
441            body,
442            wire_body_len,
443        })
444}