Skip to main content

scylla_proxy/
frame.rs

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