• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 22550902417

01 Mar 2026 07:32PM UTC coverage: 68.401% (-5.3%) from 73.687%
22550902417

Pull #14922

github

web-flow
github-actions: bump actions/upload-artifact from 6.0.0 to 7.0.0

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #14922: github-actions: bump actions/upload-artifact from 6.0.0 to 7.0.0

218243 of 319063 relevant lines covered (68.4%)

3284926.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.29
/rust/src/pgsql/pgsql.rs
1
/* Copyright (C) 2022-2025 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
// Author: Juliana Fajardini <jufajardini@oisf.net>
19

20
//! PostgreSQL parser
21

22
use super::parser::PgsqlParseError;
23
use super::parser::{self, ConsolidatedDataRowPacket, PgsqlBEMessage, PgsqlFEMessage};
24
use crate::applayer::*;
25
use crate::conf::*;
26
use crate::core::{ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP, *};
27
use crate::direction::Direction;
28
use crate::flow::Flow;
29
use nom8::{Err, IResult};
30
use std;
31
use std::collections::VecDeque;
32
use std::ffi::CString;
33
use suricata_sys::sys::{
34
    AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled,
35
    SCAppLayerParserSetStreamDepth, SCAppLayerParserStateIssetFlag,
36
    SCAppLayerProtoDetectConfProtoDetectionEnabled, SCAppLayerRequestProtocolTLSUpgrade,
37
};
38

39
const PGSQL_CONFIG_DEFAULT_STREAM_DEPTH: u32 = 0;
40

41
pub(crate) static mut ALPROTO_PGSQL: AppProto = ALPROTO_UNKNOWN;
42

43
static mut PGSQL_MAX_TX: usize = 1024;
44

45
#[derive(AppLayerEvent, Debug, PartialEq, Eq)]
×
46
enum PgsqlEvent {
47
    InvalidLength,     // Can't parse the length field
48
    MalformedRequest,  // Enough data, but unexpected request format
49
    MalformedResponse, // Enough data, but unexpected response format
50
    TooManyTransactions,
51
}
52

53
#[repr(u8)]
54
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)]
55
pub(crate) enum PgsqlTxProgress {
56
    Init = 0,
57
    Received,
58
    Done,
59
    FlushedOut,
60
}
61

62
#[derive(Debug)]
63
pub(crate) struct PgsqlTransaction {
64
    pub tx_id: u64,
65
    pub tx_req_state: PgsqlTxProgress,
66
    pub tx_res_state: PgsqlTxProgress,
67
    pub requests: Vec<PgsqlFEMessage>,
68
    pub responses: Vec<PgsqlBEMessage>,
69

70
    pub data_row_cnt: u64,
71
    pub data_size: u64,
72

73
    tx_data: AppLayerTxData,
74
}
75

76
impl Transaction for PgsqlTransaction {
77
    fn id(&self) -> u64 {
×
78
        self.tx_id
×
79
    }
×
80
}
81

82
impl Default for PgsqlTransaction {
83
    fn default() -> Self {
×
84
        Self::new()
×
85
    }
×
86
}
87

88
impl PgsqlTransaction {
89
    fn new() -> Self {
3✔
90
        Self {
3✔
91
            tx_id: 0,
3✔
92
            tx_req_state: PgsqlTxProgress::Init,
3✔
93
            tx_res_state: PgsqlTxProgress::Init,
3✔
94
            requests: Vec::<PgsqlFEMessage>::new(),
3✔
95
            responses: Vec::<PgsqlBEMessage>::new(),
3✔
96
            data_row_cnt: 0,
3✔
97
            data_size: 0,
3✔
98
            tx_data: AppLayerTxData::new(),
3✔
99
        }
3✔
100
    }
3✔
101

102
    fn incr_row_cnt(&mut self) {
1✔
103
        self.data_row_cnt = self.data_row_cnt.saturating_add(1);
1✔
104
    }
1✔
105

106
    fn get_row_cnt(&self) -> u64 {
2✔
107
        self.data_row_cnt
2✔
108
    }
2✔
109

110
    fn sum_data_size(&mut self, row_size: u64) {
×
111
        self.data_size += row_size;
×
112
    }
×
113
}
114

115
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116
enum PgsqlStateProgress {
117
    IdleState,
118
    // Related to Frontend-received messages //
119
    SSLRequestReceived,
120
    StartupMessageReceived,
121
    SASLInitialResponseReceived,
122
    SASLResponseReceived,
123
    PasswordMessageReceived,
124
    SimpleQueryReceived,
125
    CancelRequestReceived,
126
    ConnectionTerminated,
127
    // Related to Backend-received messages //
128
    CopyDoneReceived, // BE and FE
129
    CopyFailReceived, // BE and FE
130
    CopyOutResponseReceived,
131
    CopyDataOutReceived,
132
    CopyInResponseReceived,
133
    FirstCopyDataInReceived,
134
    ConsolidatingCopyDataIn,
135
    SSLRejectedReceived,
136
    // SSPIAuthenticationReceived, // TODO implement
137
    SASLAuthenticationReceived,
138
    SASLAuthenticationContinueReceived,
139
    SASLAuthenticationFinalReceived,
140
    SimpleAuthenticationReceived,
141
    AuthenticationOkReceived,
142
    ParameterSetup,
143
    BackendKeyReceived,
144
    ReadyForQueryReceived,
145
    RowDescriptionReceived,
146
    DataRowReceived,
147
    CommandCompletedReceived,
148
    ErrorMessageReceived,
149
    #[cfg(test)]
150
    UnknownState,
151
    Finished,
152
}
153

154
#[derive(Debug)]
155
struct PgsqlState {
156
    state_data: AppLayerStateData,
157
    tx_id: u64,
158
    transactions: VecDeque<PgsqlTransaction>,
159
    request_gap: bool,
160
    response_gap: bool,
161
    backend_secret_key: u32,
162
    backend_pid: u32,
163
    state_progress: PgsqlStateProgress,
164
    tx_index_completed: usize,
165
}
166

167
impl State<PgsqlTransaction> for PgsqlState {
168
    fn get_transaction_count(&self) -> usize {
×
169
        self.transactions.len()
×
170
    }
×
171

172
    fn get_transaction_by_index(&self, index: usize) -> Option<&PgsqlTransaction> {
×
173
        self.transactions.get(index)
×
174
    }
×
175
}
176

177
impl Default for PgsqlState {
178
    fn default() -> Self {
×
179
        Self::new()
×
180
    }
×
181
}
182

183
impl PgsqlState {
184
    fn new() -> Self {
3✔
185
        Self {
3✔
186
            state_data: AppLayerStateData::default(),
3✔
187
            tx_id: 0,
3✔
188
            transactions: VecDeque::new(),
3✔
189
            request_gap: false,
3✔
190
            response_gap: false,
3✔
191
            backend_secret_key: 0,
3✔
192
            backend_pid: 0,
3✔
193
            state_progress: PgsqlStateProgress::IdleState,
3✔
194
            tx_index_completed: 0,
3✔
195
        }
3✔
196
    }
3✔
197

198
    // Free a transaction by ID.
199
    fn free_tx(&mut self, tx_id: u64) {
×
200
        let len = self.transactions.len();
×
201
        let mut found = false;
×
202
        let mut index = 0;
×
203
        for i in 0..len {
×
204
            let tx = &self.transactions[i];
×
205
            if tx.tx_id == tx_id + 1 {
×
206
                found = true;
×
207
                index = i;
×
208
                break;
×
209
            }
×
210
        }
211
        if found {
×
212
            self.tx_index_completed = 0;
×
213
            self.transactions.remove(index);
×
214
        }
×
215
    }
×
216

217
    fn get_tx(&mut self, tx_id: u64) -> Option<&PgsqlTransaction> {
×
218
        self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1)
×
219
    }
×
220

221
    fn new_tx(&mut self) -> PgsqlTransaction {
2✔
222
        let mut tx = PgsqlTransaction::new();
2✔
223
        self.tx_id += 1;
2✔
224
        tx.tx_id = self.tx_id;
2✔
225
        SCLogDebug!("Creating new transaction. tx_id: {}", tx.tx_id);
2✔
226
        if self.transactions.len() > unsafe { PGSQL_MAX_TX } + self.tx_index_completed {
2✔
227
            // If there are too many open transactions,
228
            // mark the earliest ones as completed, and take care
229
            // to avoid quadratic complexity
230
            let mut index = self.tx_index_completed;
×
231
            for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) {
×
232
                index += 1;
×
233
                if tx_old.tx_res_state < PgsqlTxProgress::Done {
×
234
                    tx_old.tx_data.0.updated_tc = true;
×
235
                    tx_old.tx_data.0.updated_ts = true;
×
236
                    // we don't check for TxReqDone for the majority of requests are basically completed
×
237
                    // when they're parsed, as of now
×
238
                    tx_old.tx_req_state = PgsqlTxProgress::FlushedOut;
×
239
                    tx_old.tx_res_state = PgsqlTxProgress::FlushedOut;
×
240
                    tx_old
×
241
                        .tx_data
×
242
                        .set_event(PgsqlEvent::TooManyTransactions as u8);
×
243
                    break;
×
244
                }
×
245
            }
246
            self.tx_index_completed = index;
×
247
        }
2✔
248
        return tx;
2✔
249
    }
2✔
250

251
    /// Find or create a new transaction
252
    ///
253
    /// If a new transaction is created, push that into state.transactions before returning &mut to last tx
254
    /// If we can't find a transaction and we should not create one, we return None
255
    /// The moment when this is called will may impact the logic of transaction tracking (e.g. when a tx is considered completed)
256
    // TODO A future, improved version may be based on current message type and dir, too
257
    fn find_or_create_tx(&mut self) -> Option<&mut PgsqlTransaction> {
4✔
258
        // First, check if we should create a new tx (in case the other was completed or there's no tx yet)
4✔
259
        if self.state_progress == PgsqlStateProgress::IdleState
4✔
260
            || self.state_progress == PgsqlStateProgress::StartupMessageReceived
3✔
261
            || self.state_progress == PgsqlStateProgress::PasswordMessageReceived
3✔
262
            || self.state_progress == PgsqlStateProgress::SASLInitialResponseReceived
3✔
263
            || self.state_progress == PgsqlStateProgress::SASLResponseReceived
3✔
264
            || self.state_progress == PgsqlStateProgress::SimpleQueryReceived
3✔
265
            || self.state_progress == PgsqlStateProgress::SSLRequestReceived
3✔
266
            || self.state_progress == PgsqlStateProgress::ConnectionTerminated
2✔
267
            || self.state_progress == PgsqlStateProgress::CancelRequestReceived
2✔
268
            || self.state_progress == PgsqlStateProgress::FirstCopyDataInReceived
2✔
269
        {
2✔
270
            let tx = self.new_tx();
2✔
271
            self.transactions.push_back(tx);
2✔
272
        }
2✔
273
        // If we don't need a new transaction, just return the current one
274
        SCLogDebug!("find_or_create state is {:?}", &self.state_progress);
275
        return self.transactions.back_mut();
4✔
276
    }
4✔
277

278
    fn get_curr_state(&mut self) -> PgsqlStateProgress {
×
279
        self.state_progress
×
280
    }
×
281

282
    /// Define PgsqlState progression, based on the request received
283
    ///
284
    /// As PostgreSQL transactions can have multiple messages, State progression
285
    /// is what helps us keep track of the PgsqlTransactions - when one finished
286
    /// when the other starts.
287
    /// State isn't directly updated to avoid reference borrowing conflicts.
288
    fn request_next_state(&mut self, request: &PgsqlFEMessage) -> Option<PgsqlStateProgress> {
1✔
289
        match request {
1✔
290
            PgsqlFEMessage::SSLRequest(_) => Some(PgsqlStateProgress::SSLRequestReceived),
1✔
291
            PgsqlFEMessage::StartupMessage(_) => Some(PgsqlStateProgress::StartupMessageReceived),
×
292
            PgsqlFEMessage::PasswordMessage(_) => Some(PgsqlStateProgress::PasswordMessageReceived),
×
293
            PgsqlFEMessage::SASLInitialResponse(_) => {
294
                Some(PgsqlStateProgress::SASLInitialResponseReceived)
×
295
            }
296
            PgsqlFEMessage::SASLResponse(_) => Some(PgsqlStateProgress::SASLResponseReceived),
×
297
            PgsqlFEMessage::SimpleQuery(_) => {
298
                SCLogDebug!("Match: SimpleQuery");
299
                Some(PgsqlStateProgress::SimpleQueryReceived)
×
300
                // TODO here we may want to save the command that was received, to compare that later on when we receive command completed?
301

302
                // Important to keep in mind that: "In simple Query mode, the format of retrieved values is always text, except when the given command is a FETCH from a cursor declared with the BINARY option. In that case, the retrieved values are in binary format. The format codes given in the RowDescription message tell which format is being used." (from pgsql official documentation)
303
            }
304
            PgsqlFEMessage::ConsolidatedCopyDataIn(_) => {
305
                match self.get_curr_state() {
×
306
                    PgsqlStateProgress::CopyInResponseReceived => {
307
                        return Some(PgsqlStateProgress::FirstCopyDataInReceived);
×
308
                    }
309
                    PgsqlStateProgress::FirstCopyDataInReceived
310
                    | PgsqlStateProgress::ConsolidatingCopyDataIn => {
311
                        // We are in CopyInResponseReceived state, and we received a CopyDataIn message
312
                        // We can either be in the first CopyDataIn message or in the middle
313
                        // of consolidating CopyDataIn messages
314
                        return Some(PgsqlStateProgress::ConsolidatingCopyDataIn);
×
315
                    }
316
                    _ => {
317
                        return None;
×
318
                    }
319
                }
320
            }
321
            PgsqlFEMessage::CopyDone(_) => Some(PgsqlStateProgress::CopyDoneReceived),
×
322
            PgsqlFEMessage::CopyFail(_) => Some(PgsqlStateProgress::CopyFailReceived),
×
323
            PgsqlFEMessage::CancelRequest(_) => Some(PgsqlStateProgress::CancelRequestReceived),
×
324
            PgsqlFEMessage::Terminate(_) => {
325
                SCLogDebug!("Match: Terminate message");
326
                Some(PgsqlStateProgress::ConnectionTerminated)
×
327
            }
328
            PgsqlFEMessage::UnknownMessageType(_) => {
329
                SCLogDebug!("Match: Unknown request message type");
330
                // Not changing state when we don't know the message
331
                None
×
332
            }
333
        }
334
    }
1✔
335

336
    fn state_based_req_parsing(
3✔
337
        state: PgsqlStateProgress, input: &[u8],
3✔
338
    ) -> IResult<&[u8], parser::PgsqlFEMessage, PgsqlParseError<&[u8]>> {
3✔
339
        match state {
3✔
340
            PgsqlStateProgress::SASLAuthenticationReceived => {
341
                parser::parse_sasl_initial_response(input)
×
342
            }
343
            PgsqlStateProgress::SASLInitialResponseReceived
344
            | PgsqlStateProgress::SASLAuthenticationContinueReceived => {
345
                parser::parse_sasl_response(input)
×
346
            }
347
            PgsqlStateProgress::SimpleAuthenticationReceived => {
348
                parser::parse_password_message(input)
×
349
            }
350
            _ => parser::parse_request(input),
3✔
351
        }
352
    }
3✔
353

354
    /// Process State progress to decide if request is finished
355
    ///
356
    fn request_is_complete(state: PgsqlStateProgress) -> bool {
1✔
357
        match state {
1✔
358
            PgsqlStateProgress::SSLRequestReceived
359
            | PgsqlStateProgress::StartupMessageReceived
360
            | PgsqlStateProgress::SimpleQueryReceived
361
            | PgsqlStateProgress::PasswordMessageReceived
362
            | PgsqlStateProgress::SASLInitialResponseReceived
363
            | PgsqlStateProgress::SASLResponseReceived
364
            | PgsqlStateProgress::CancelRequestReceived
365
            | PgsqlStateProgress::CopyDoneReceived
366
            | PgsqlStateProgress::CopyFailReceived
367
            | PgsqlStateProgress::ConnectionTerminated => true,
1✔
368
            _ => false,
×
369
        }
370
    }
1✔
371

372
    fn parse_request(&mut self, flow: *mut Flow, input: &[u8]) -> AppLayerResult {
4✔
373
        // We're not interested in empty requests.
4✔
374
        if input.is_empty() {
4✔
375
            return AppLayerResult::ok();
1✔
376
        }
3✔
377

3✔
378
        // If there was gap, check we can sync up again.
3✔
379
        if self.request_gap {
3✔
380
            if parser::parse_request(input).is_ok() {
×
381
                // The parser now needs to decide what to do as we are not in sync.
382
                // For now, we'll just try again next time.
383
                SCLogDebug!("Suricata interprets there's a gap in the request");
384
                return AppLayerResult::ok();
×
385
            }
×
386

×
387
            // It looks like we're in sync with the message header
×
388
            // clear gap state and keep parsing.
×
389
            self.request_gap = false;
×
390
        }
3✔
391

392
        let mut start = input;
3✔
393
        while !start.is_empty() {
4✔
394
            SCLogDebug!(
395
                "In 'parse_request' State Progress is: {:?}",
396
                &self.state_progress
397
            );
398
            match PgsqlState::state_based_req_parsing(self.state_progress, start) {
3✔
399
                Ok((rem, request)) => {
1✔
400
                    start = rem;
1✔
401
                    let new_state = self.request_next_state(&request);
1✔
402

403
                    if let Some(state) = new_state {
1✔
404
                        self.state_progress = state;
1✔
405
                    };
1✔
406
                    // PostreSQL progress states can be represented as a finite state machine
407
                    // After the connection phase, the backend/ server will be mostly waiting in a state of `ReadyForQuery`, unless
408
                    // it's processing some request.
409
                    // When the frontend wants to cancel a request, it will send a CancelRequest message over a new connection - to
410
                    // which there won't be any responses.
411
                    // If the frontend wants to terminate the connection, the backend won't send any confirmation after receiving a
412
                    // Terminate request.
413
                    // A simplified finite state machine for PostgreSQL v3 can be found at:
414
                    // https://samadhiweb.com/blog/2013.04.28.graphviz.postgresv3.html
415
                    if let Some(tx) = self.find_or_create_tx() {
1✔
416
                        tx.tx_data.0.updated_ts = true;
1✔
417
                        if let Some(state) = new_state {
1✔
418
                            if state == PgsqlStateProgress::FirstCopyDataInReceived
1✔
419
                            || state == PgsqlStateProgress::ConsolidatingCopyDataIn {
1✔
420
                                // here we're actually only counting how many messages were received.
421
                                // frontends are not forced to send one row per message
422
                                if let PgsqlFEMessage::ConsolidatedCopyDataIn(ref msg) = request {
×
423
                                    tx.sum_data_size(msg.data_size);
×
424
                                    tx.incr_row_cnt();
×
425
                                }
×
426
                            } else if (state == PgsqlStateProgress::CopyDoneReceived || state == PgsqlStateProgress::CopyFailReceived) && tx.get_row_cnt() > 0 {
1✔
427
                                let consolidated_copy_data = PgsqlFEMessage::ConsolidatedCopyDataIn(
×
428
                                    ConsolidatedDataRowPacket {
×
429
                                        identifier: b'd',
×
430
                                        row_cnt: tx.get_row_cnt(),
×
431
                                        data_size: tx.data_size, // total byte count of all copy_data messages combined
×
432
                                    },
×
433
                                );
×
434
                                tx.requests.push(consolidated_copy_data);
×
435
                            }
1✔
436

437
                            if Self::request_is_complete(state) {
1✔
438
                                tx.requests.push(request);
1✔
439
                                // The request is complete at this point
1✔
440
                                tx.tx_req_state = PgsqlTxProgress::Done;
1✔
441
                                if state == PgsqlStateProgress::ConnectionTerminated
1✔
442
                                    || state == PgsqlStateProgress::CancelRequestReceived
1✔
443
                                {
×
444
                                    /* The server won't send any responses to such requests, so transaction should be over */
×
445
                                    tx.tx_res_state = PgsqlTxProgress::Done;
×
446
                                }
1✔
447
                                sc_app_layer_parser_trigger_raw_stream_inspection(
1✔
448
                                    flow,
1✔
449
                                    Direction::ToServer as i32,
1✔
450
                                );
1✔
451
                            }
×
452
                        }
×
453
                    } else {
454
                        // If there isn't a transaction, we'll consider Suri should move on
455
                        return AppLayerResult::ok();
×
456
                    };
457
                }
458
                Err(Err::Incomplete(_needed)) => {
2✔
459
                    let consumed = input.len() - start.len();
2✔
460
                    let needed_estimation = start.len() + 1;
2✔
461
                    SCLogDebug!(
2✔
462
                        "Needed: {:?}, estimated needed: {:?}",
2✔
463
                        _needed,
2✔
464
                        needed_estimation
2✔
465
                    );
2✔
466
                    return AppLayerResult::incomplete(consumed as u32, needed_estimation as u32);
2✔
467
                }
468
                Err(Err::Error(err)) => {
×
469
                    let mut tx = self.new_tx();
×
470
                    match err {
×
471
                        PgsqlParseError::InvalidLength => {
472
                            tx.tx_data.set_event(PgsqlEvent::InvalidLength as u8);
×
473
                            self.transactions.push_back(tx);
×
474
                            // If we don't get a valid length, we can't know how to proceed
×
475
                            return AppLayerResult::err();
×
476
                        }
477
                        PgsqlParseError::NomError(_i, error_kind) => {
×
478
                            if error_kind == nom8::error::ErrorKind::Switch {
×
479
                                tx.tx_data.set_event(PgsqlEvent::MalformedRequest as u8);
×
480
                                self.transactions.push_back(tx);
×
481
                            }
×
482
                            SCLogDebug!("Parsing error: {:?}", error_kind);
483
                        }
484
                    }
485
                    // If we have parsed the message length, let's assume we can
486
                    // move onto the next PDU even if we can't parse the current message
487
                    return AppLayerResult::ok();
×
488
                }
489
                Err(_) => {
490
                    SCLogDebug!("Error while parsing PGSQL request");
491
                    return AppLayerResult::err();
×
492
                }
493
            }
494
        }
495

496
        // Input was fully consumed.
497
        return AppLayerResult::ok();
1✔
498
    }
4✔
499

500
    /// When the state changes based on a specific response, there are other actions we may need to perform
501
    ///
502
    /// If there is data from the backend message that Suri should store separately in the State or
503
    /// Transaction, that is also done here
504
    fn response_process_next_state(
×
505
        &mut self, response: &PgsqlBEMessage, f: *mut Flow,
×
506
    ) -> Option<PgsqlStateProgress> {
×
507
        match response {
×
508
            PgsqlBEMessage::SSLResponse(parser::SSLResponseMessage::SSLAccepted) => {
509
                SCLogDebug!("SSL Request accepted");
510
                unsafe {
×
511
                    SCAppLayerRequestProtocolTLSUpgrade(f);
×
512
                }
×
513
                Some(PgsqlStateProgress::Finished)
×
514
            }
515
            PgsqlBEMessage::SSLResponse(parser::SSLResponseMessage::SSLRejected) => {
516
                SCLogDebug!("SSL Request rejected");
517
                Some(PgsqlStateProgress::SSLRejectedReceived)
×
518
            }
519
            PgsqlBEMessage::AuthenticationSASL(_) => {
520
                Some(PgsqlStateProgress::SASLAuthenticationReceived)
×
521
            }
522
            PgsqlBEMessage::AuthenticationSASLContinue(_) => {
523
                Some(PgsqlStateProgress::SASLAuthenticationContinueReceived)
×
524
            }
525
            PgsqlBEMessage::AuthenticationSASLFinal(_) => {
526
                Some(PgsqlStateProgress::SASLAuthenticationFinalReceived)
×
527
            }
528
            PgsqlBEMessage::AuthenticationOk(_) => {
529
                Some(PgsqlStateProgress::AuthenticationOkReceived)
×
530
            }
531
            PgsqlBEMessage::ParameterStatus(_) => Some(PgsqlStateProgress::ParameterSetup),
×
532
            PgsqlBEMessage::BackendKeyData(_) => {
533
                let backend_info = response.get_backendkey_info();
×
534
                self.backend_pid = backend_info.0;
×
535
                self.backend_secret_key = backend_info.1;
×
536
                Some(PgsqlStateProgress::BackendKeyReceived)
×
537
            }
538
            PgsqlBEMessage::ReadyForQuery(_) => Some(PgsqlStateProgress::ReadyForQueryReceived),
×
539
            // TODO should we store any Parameter Status in PgsqlState?
540
            // TODO -- For CopyBoth mode, parameterstatus may be important (replication parameter)
541
            PgsqlBEMessage::AuthenticationMD5Password(_)
542
            | PgsqlBEMessage::AuthenticationCleartextPassword(_) => {
543
                Some(PgsqlStateProgress::SimpleAuthenticationReceived)
×
544
            }
545
            PgsqlBEMessage::RowDescription(_) => Some(PgsqlStateProgress::RowDescriptionReceived),
×
546
            PgsqlBEMessage::CopyOutResponse(_) => Some(PgsqlStateProgress::CopyOutResponseReceived),
×
547
            PgsqlBEMessage::CopyInResponse(_) => Some(PgsqlStateProgress::CopyInResponseReceived),
×
548
            PgsqlBEMessage::ConsolidatedDataRow(msg) => {
×
549
                // Increment tx.data_size here, since we know msg type, so that we can later on log that info
×
550
                self.transactions.back_mut()?.sum_data_size(msg.data_size);
×
551
                Some(PgsqlStateProgress::DataRowReceived)
×
552
            }
553
            PgsqlBEMessage::ConsolidatedCopyDataOut(msg) => {
×
554
                // Increment tx.data_size here, since we know msg type, so that we can later on log that info
×
555
                self.transactions.back_mut()?.sum_data_size(msg.data_size);
×
556
                Some(PgsqlStateProgress::CopyDataOutReceived)
×
557
            }
558
            PgsqlBEMessage::CopyDone(_) => Some(PgsqlStateProgress::CopyDoneReceived),
×
559
            PgsqlBEMessage::CommandComplete(_) => {
560
                // TODO Do we want to compare the command that was stored when
561
                // query was sent with what we received here?
562
                Some(PgsqlStateProgress::CommandCompletedReceived)
×
563
            }
564
            PgsqlBEMessage::UnknownMessageType(_) => {
565
                SCLogDebug!("Match: Unknown response message type");
566
                // Not changing state when we don't know the message
567
                None
×
568
            }
569
            PgsqlBEMessage::ErrorResponse(_) => Some(PgsqlStateProgress::ErrorMessageReceived),
×
570
            _ => {
571
                // We don't always have to change current state when we see a response...
572
                // NotificationResponse and NoticeResponse fall here
573
                None
×
574
            }
575
        }
576
    }
×
577

578
    fn state_based_resp_parsing(
×
579
        state: PgsqlStateProgress, input: &[u8],
×
580
    ) -> IResult<&[u8], parser::PgsqlBEMessage, PgsqlParseError<&[u8]>> {
×
581
        if state == PgsqlStateProgress::SSLRequestReceived {
×
582
            parser::parse_ssl_response(input)
×
583
        } else {
584
            parser::pgsql_parse_response(input)
×
585
        }
586
    }
×
587

588
    /// Process State progress to decide if response is finished
589
    ///
590
    fn response_is_complete(state: PgsqlStateProgress) -> bool {
×
591
        match state {
×
592
            PgsqlStateProgress::ReadyForQueryReceived
593
            | PgsqlStateProgress::SSLRejectedReceived
594
            | PgsqlStateProgress::SimpleAuthenticationReceived
595
            | PgsqlStateProgress::SASLAuthenticationReceived
596
            | PgsqlStateProgress::SASLAuthenticationContinueReceived
597
            | PgsqlStateProgress::SASLAuthenticationFinalReceived
598
            | PgsqlStateProgress::CopyInResponseReceived
599
            | PgsqlStateProgress::Finished => true,
×
600
            _ => false,
×
601
        }
602
    }
×
603

604
    fn parse_response(&mut self, flow: *mut Flow, input: &[u8]) -> AppLayerResult {
×
605
        // We're not interested in empty responses.
×
606
        if input.is_empty() {
×
607
            return AppLayerResult::ok();
×
608
        }
×
609

×
610
        if self.response_gap {
×
611
            if !probe_tc(input) {
×
612
                // Out of sync, we'll just try again next time.
613
                SCLogDebug!("Suricata interprets there's a gap in the response");
614
                return AppLayerResult::ok();
×
615
            }
×
616

×
617
            // It seems we're in sync with a message header, clear gap state and keep parsing.
×
618
            self.response_gap = false;
×
619
        }
×
620

621
        let mut start = input;
×
622
        while !start.is_empty() {
×
623
            match PgsqlState::state_based_resp_parsing(self.state_progress, start) {
×
624
                Ok((rem, response)) => {
×
625
                    start = rem;
×
626
                    SCLogDebug!("Response is {:?}", &response);
×
627
                    let new_state = self.response_process_next_state(&response, flow);
×
628
                    if let Some(state) = new_state {
×
629
                        self.state_progress = state;
×
630
                    }
×
631
                    if let Some(tx) = self.find_or_create_tx() {
×
632
                        tx.tx_data.0.updated_tc = true;
×
633
                        if tx.tx_res_state == PgsqlTxProgress::Init {
×
634
                            tx.tx_res_state = PgsqlTxProgress::Received;
×
635
                        }
×
636
                        if let Some(state) = new_state {
×
637
                            if state == PgsqlStateProgress::DataRowReceived {
×
638
                                tx.incr_row_cnt();
×
639
                            } else if state == PgsqlStateProgress::CommandCompletedReceived
×
640
                                && tx.get_row_cnt() > 0
×
641
                            {
×
642
                                // let's summarize the info from the data_rows in one response
×
643
                                let consolidated_data_row = PgsqlBEMessage::ConsolidatedDataRow(
×
644
                                    ConsolidatedDataRowPacket {
×
645
                                        identifier: b'D',
×
646
                                        row_cnt: tx.get_row_cnt(),
×
647
                                        data_size: tx.data_size, // total byte count of all data_row messages combined
×
648
                                    },
×
649
                                );
×
650
                                tx.responses.push(consolidated_data_row);
×
651
                                tx.responses.push(response);
×
652
                                // reset values
×
653
                                tx.data_row_cnt = 0;
×
654
                                tx.data_size = 0;
×
655
                            } else if state == PgsqlStateProgress::CopyDataOutReceived {
×
656
                                tx.incr_row_cnt();
×
657
                            } else if state == PgsqlStateProgress::CopyDoneReceived
×
658
                                && tx.get_row_cnt() > 0
×
659
                            {
×
660
                                // let's summarize the info from the data_rows in one response
×
661
                                let consolidated_copy_data = PgsqlBEMessage::ConsolidatedCopyDataOut(
×
662
                                    ConsolidatedDataRowPacket {
×
663
                                        identifier: b'd',
×
664
                                        row_cnt: tx.get_row_cnt(),
×
665
                                        data_size: tx.data_size, // total byte count of all data_row messages combined
×
666
                                    },
×
667
                                );
×
668
                                tx.responses.push(consolidated_copy_data);
×
669
                                tx.responses.push(response);
×
670
                                // reset values
×
671
                                tx.data_row_cnt = 0;
×
672
                                tx.data_size = 0;
×
673
                            } else {
×
674
                                tx.responses.push(response);
×
675
                                if Self::response_is_complete(state) {
×
676
                                    tx.tx_req_state = PgsqlTxProgress::Done;
×
677
                                    tx.tx_res_state = PgsqlTxProgress::Done;
×
678
                                    sc_app_layer_parser_trigger_raw_stream_inspection(
×
679
                                        flow,
×
680
                                        Direction::ToClient as i32,
×
681
                                    );
×
682
                                }
×
683
                            }
684
                        }
×
685
                    } else {
686
                        // If there isn't a transaction, we'll consider Suri should move on
687
                        return AppLayerResult::ok();
×
688
                    };
689
                }
690
                Err(Err::Incomplete(_needed)) => {
×
691
                    let consumed = input.len() - start.len();
×
692
                    let needed_estimation = start.len() + 1;
×
693
                    SCLogDebug!(
×
694
                        "Needed: {:?}, estimated needed: {:?}, start is {:?}",
×
695
                        _needed,
×
696
                        needed_estimation,
×
697
                        &start
×
698
                    );
×
699
                    return AppLayerResult::incomplete(consumed as u32, needed_estimation as u32);
×
700
                }
701
                Err(Err::Error(err)) => {
×
702
                    let mut tx = self.new_tx();
×
703
                    match err {
×
704
                        PgsqlParseError::InvalidLength => {
705
                            tx.tx_data.set_event(PgsqlEvent::InvalidLength as u8);
×
706
                            self.transactions.push_back(tx);
×
707
                            // If we don't get a valid length, we can't know how to proceed
×
708
                            return AppLayerResult::err();
×
709
                        }
710
                        PgsqlParseError::NomError(_i, error_kind) => {
×
711
                            if error_kind == nom8::error::ErrorKind::Switch {
×
712
                                tx.tx_data.set_event(PgsqlEvent::MalformedResponse as u8);
×
713
                                self.transactions.push_back(tx);
×
714
                            }
×
715
                            SCLogDebug!("Parsing error: {:?}", error_kind);
716
                        }
717
                    }
718
                    // If we have parsed the message length, let's assume we can
719
                    // move onto the next PDU even if we can't parse the current message
720
                    return AppLayerResult::ok();
×
721
                }
722
                Err(_) => {
723
                    SCLogDebug!("Error while parsing PGSQL response");
724
                    return AppLayerResult::err();
×
725
                }
726
            }
727
        }
728

729
        // All input was fully consumed.
730
        return AppLayerResult::ok();
×
731
    }
×
732

733
    fn on_request_gap(&mut self, _size: u32) {
×
734
        self.request_gap = true;
×
735
    }
×
736

737
    fn on_response_gap(&mut self, _size: u32) {
×
738
        self.response_gap = true;
×
739
    }
×
740
}
741

742
/// Probe for a valid PostgreSQL response
743
///
744
/// Currently, for parser usage only. We have a bit more logic in the function
745
/// used by the engine.
746
/// PGSQL messages don't have a header per se, so we parse the slice for an ok()
747
fn probe_tc(input: &[u8]) -> bool {
4✔
748
    if parser::pgsql_parse_response(input).is_ok() || parser::parse_ssl_response(input).is_ok() {
4✔
749
        return true;
4✔
750
    }
×
751
    SCLogDebug!("probe_tc is false");
×
752
    false
×
753
}
4✔
754

755
fn pgsql_tx_get_req_state(tx: *mut std::os::raw::c_void) -> PgsqlTxProgress {
×
756
    let tx_safe: &mut PgsqlTransaction;
×
757
    unsafe {
×
758
        tx_safe = cast_pointer!(tx, PgsqlTransaction);
×
759
    }
×
760
    tx_safe.tx_req_state
×
761
}
×
762

763
fn pgsql_tx_get_res_state(tx: *mut std::os::raw::c_void) -> PgsqlTxProgress {
×
764
    let tx_safe: &mut PgsqlTransaction;
×
765
    unsafe {
×
766
        tx_safe = cast_pointer!(tx, PgsqlTransaction);
×
767
    }
×
768
    tx_safe.tx_res_state
×
769
}
×
770

771
// C exports.
772

773
/// C entry point for a probing parser.
774
unsafe extern "C" fn probing_parser_ts(
×
775
    _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
×
776
) -> AppProto {
×
777
    if input_len >= 1 && !input.is_null() {
×
778
        let slice: &[u8] = build_slice!(input, input_len as usize);
×
779

×
780
        match parser::parse_request(slice) {
×
781
            Ok((_, _)) => {
782
                return ALPROTO_PGSQL;
×
783
            }
784
            Err(Err::Incomplete(_)) => {
785
                return ALPROTO_UNKNOWN;
×
786
            }
787
            Err(_e) => {
×
788
                return ALPROTO_FAILED;
×
789
            }
790
        }
791
    }
×
792
    return ALPROTO_UNKNOWN;
×
793
}
×
794

795
/// C entry point for a probing parser.
796
unsafe extern "C" fn probing_parser_tc(
×
797
    _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
×
798
) -> AppProto {
×
799
    if input_len >= 1 && !input.is_null() {
×
800
        let slice: &[u8] = build_slice!(input, input_len as usize);
×
801

×
802
        if parser::parse_ssl_response(slice).is_ok() {
×
803
            return ALPROTO_PGSQL;
×
804
        }
×
805

×
806
        match parser::pgsql_parse_response(slice) {
×
807
            Ok((_, _)) => {
808
                return ALPROTO_PGSQL;
×
809
            }
810
            Err(Err::Incomplete(_)) => {
811
                return ALPROTO_UNKNOWN;
×
812
            }
813
            Err(_e) => {
×
814
                return ALPROTO_FAILED;
×
815
            }
816
        }
817
    }
×
818
    return ALPROTO_UNKNOWN;
×
819
}
×
820

821
extern "C" fn state_new(
×
822
    _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
×
823
) -> *mut std::os::raw::c_void {
×
824
    let state = PgsqlState::new();
×
825
    let boxed = Box::new(state);
×
826
    return Box::into_raw(boxed) as *mut _;
×
827
}
×
828

829
extern "C" fn state_free(state: *mut std::os::raw::c_void) {
×
830
    // Just unbox...
×
831
    std::mem::drop(unsafe { Box::from_raw(state as *mut PgsqlState) });
×
832
}
×
833

834
unsafe extern "C" fn state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
×
835
    let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState);
×
836
    state_safe.free_tx(tx_id);
×
837
}
×
838

839
unsafe extern "C" fn parse_request(
×
840
    flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
×
841
    stream_slice: StreamSlice, _data: *mut std::os::raw::c_void,
×
842
) -> AppLayerResult {
×
843
    if stream_slice.is_empty() {
×
844
        if SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 {
×
845
            SCLogDebug!(" Suricata reached `eof`");
846
            return AppLayerResult::ok();
×
847
        } else {
848
            return AppLayerResult::err();
×
849
        }
850
    }
×
851

×
852
    let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState);
×
853

×
854
    if stream_slice.is_gap() {
×
855
        state_safe.on_request_gap(stream_slice.gap_size());
×
856
    } else if !stream_slice.is_empty() {
×
857
        return state_safe.parse_request(flow, stream_slice.as_slice());
×
858
    }
×
859
    AppLayerResult::ok()
×
860
}
×
861

862
unsafe extern "C" fn parse_response(
×
863
    flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
×
864
    stream_slice: StreamSlice, _data: *mut std::os::raw::c_void,
×
865
) -> AppLayerResult {
×
866
    if stream_slice.is_empty() {
×
867
        if SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 {
×
868
            return AppLayerResult::ok();
×
869
        } else {
870
            return AppLayerResult::err();
×
871
        }
872
    }
×
873

×
874
    let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState);
×
875

×
876
    if stream_slice.is_gap() {
×
877
        state_safe.on_response_gap(stream_slice.gap_size());
×
878
    } else if !stream_slice.is_empty() {
×
879
        return state_safe.parse_response(flow, stream_slice.as_slice());
×
880
    }
×
881
    AppLayerResult::ok()
×
882
}
×
883

884
unsafe extern "C" fn state_get_tx(
×
885
    state: *mut std::os::raw::c_void, tx_id: u64,
×
886
) -> *mut std::os::raw::c_void {
×
887
    let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState);
×
888
    match state_safe.get_tx(tx_id) {
×
889
        Some(tx) => {
×
890
            return tx as *const _ as *mut _;
×
891
        }
892
        None => {
893
            return std::ptr::null_mut();
×
894
        }
895
    }
896
}
×
897

898
unsafe extern "C" fn state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
×
899
    let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState);
×
900
    return state_safe.tx_id;
×
901
}
×
902

903
unsafe extern "C" fn tx_get_al_state_progress(
×
904
    tx: *mut std::os::raw::c_void, direction: u8,
×
905
) -> std::os::raw::c_int {
×
906
    if direction == Direction::ToServer as u8 {
×
907
        return pgsql_tx_get_req_state(tx) as i32;
×
908
    }
×
909

×
910
    // Direction has only two possible values, so we don't need to check for the other one
×
911
    pgsql_tx_get_res_state(tx) as i32
×
912
}
×
913

914
export_tx_data_get!(pgsql_get_tx_data, PgsqlTransaction);
915
export_state_data_get!(pgsql_get_state_data, PgsqlState);
916

917
// Parser name as a C style string.
918
const PARSER_NAME: &[u8] = b"pgsql\0";
919

920
#[no_mangle]
921
pub unsafe extern "C" fn SCRegisterPgsqlParser() {
40✔
922
    let default_port = CString::new("[5432]").unwrap();
40✔
923
    let mut stream_depth = PGSQL_CONFIG_DEFAULT_STREAM_DEPTH;
40✔
924
    let parser = RustParser {
40✔
925
        name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
40✔
926
        default_port: default_port.as_ptr(),
40✔
927
        ipproto: IPPROTO_TCP,
40✔
928
        probe_ts: Some(probing_parser_ts),
40✔
929
        probe_tc: Some(probing_parser_tc),
40✔
930
        min_depth: 0,
40✔
931
        max_depth: 16,
40✔
932
        state_new,
40✔
933
        state_free,
40✔
934
        tx_free: state_tx_free,
40✔
935
        parse_ts: parse_request,
40✔
936
        parse_tc: parse_response,
40✔
937
        get_tx_count: state_get_tx_count,
40✔
938
        get_tx: state_get_tx,
40✔
939
        tx_comp_st_ts: PgsqlTxProgress::Done as i32,
40✔
940
        tx_comp_st_tc: PgsqlTxProgress::Done as i32,
40✔
941
        tx_get_progress: tx_get_al_state_progress,
40✔
942
        get_eventinfo: Some(PgsqlEvent::get_event_info),
40✔
943
        get_eventinfo_byid: Some(PgsqlEvent::get_event_info_by_id),
40✔
944
        localstorage_new: None,
40✔
945
        localstorage_free: None,
40✔
946
        get_tx_files: None,
40✔
947
        get_tx_iterator: Some(
40✔
948
            crate::applayer::state_get_tx_iterator::<PgsqlState, PgsqlTransaction>,
40✔
949
        ),
40✔
950
        get_tx_data: pgsql_get_tx_data,
40✔
951
        get_state_data: pgsql_get_state_data,
40✔
952
        apply_tx_config: None,
40✔
953
        flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS,
40✔
954
        get_frame_id_by_name: None,
40✔
955
        get_frame_name_by_id: None,
40✔
956
        get_state_id_by_name: None,
40✔
957
        get_state_name_by_id: None,
40✔
958
    };
40✔
959

40✔
960
    let ip_proto_str = CString::new("tcp").unwrap();
40✔
961

40✔
962
    if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
40✔
963
        let alproto = applayer_register_protocol_detection(&parser, 1);
31✔
964
        ALPROTO_PGSQL = alproto;
31✔
965
        if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
31✔
966
            let _ = AppLayerRegisterParser(&parser, alproto);
31✔
967
        }
31✔
968
        SCLogDebug!("Rust pgsql parser registered.");
969
        let retval = conf_get("app-layer.protocols.pgsql.stream-depth");
31✔
970
        if let Some(val) = retval {
31✔
971
            match get_memval(val) {
×
972
                Ok(retval) => {
×
973
                    stream_depth = retval as u32;
×
974
                }
×
975
                Err(_) => {
976
                    SCLogError!("Invalid depth value");
×
977
                }
978
            }
979
            SCAppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_PGSQL, stream_depth)
×
980
        }
31✔
981
        if let Some(val) = conf_get("app-layer.protocols.pgsql.max-tx") {
31✔
982
            if let Ok(v) = val.parse::<usize>() {
×
983
                PGSQL_MAX_TX = v;
×
984
            } else {
×
985
                SCLogError!("Invalid value for pgsql.max-tx");
×
986
            }
987
        }
31✔
988
    } else {
9✔
989
        SCLogDebug!("Protocol detector and parser disabled for PGSQL.");
9✔
990
    }
9✔
991
}
40✔
992

993
#[cfg(test)]
994
mod test {
995
    use super::*;
996

997
    #[test]
998
    fn test_response_probe() {
1✔
999
        /* Authentication Request MD5 password salt value f211a3ed */
1✔
1000
        let buf: &[u8] = &[
1✔
1001
            0x52, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x05, 0xf2, 0x11, 0xa3, 0xed,
1✔
1002
        ];
1✔
1003
        assert!(probe_tc(buf));
1✔
1004

1005
        /* R  8 -- Authentication Cleartext */
1006
        let buf: &[u8] = &[0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03];
1✔
1007
        assert!(probe_tc(buf));
1✔
1008

1009
        let buf: &[u8] = &[
1✔
1010
            /* R */ 0x52, /* 54 */ 0x00, 0x00, 0x00, 0x36, /* 12 */ 0x00, 0x00,
1✔
1011
            0x00, 0x0c, /* signature */ 0x76, 0x3d, 0x64, 0x31, 0x50, 0x58, 0x61, 0x38, 0x54,
1✔
1012
            0x4b, 0x46, 0x50, 0x5a, 0x72, 0x52, 0x33, 0x4d, 0x42, 0x52, 0x6a, 0x4c, 0x79, 0x33,
1✔
1013
            0x2b, 0x4a, 0x36, 0x79, 0x78, 0x72, 0x66, 0x77, 0x2f, 0x7a, 0x7a, 0x70, 0x38, 0x59,
1✔
1014
            0x54, 0x39, 0x65, 0x78, 0x56, 0x37, 0x73, 0x38, 0x3d,
1✔
1015
        ];
1✔
1016
        assert!(probe_tc(buf));
1✔
1017

1018
        /* S   26 -- parameter status application_name psql*/
1019
        let buf: &[u8] = &[
1✔
1020
            0x53, 0x00, 0x00, 0x00, 0x1a, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
1✔
1021
            0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x70, 0x73, 0x71, 0x6c, 0x00,
1✔
1022
        ];
1✔
1023
        assert!(probe_tc(buf));
1✔
1024
    }
1✔
1025

1026
    #[test]
1027
    fn test_request_events() {
1✔
1028
        let mut state = PgsqlState::new();
1✔
1029
        // an SSL Request
1✔
1030
        let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f];
1✔
1031
        // We can pass null here as the only place that uses flow in the parse_request fn isn't run for unittests
1✔
1032
        state.parse_request(std::ptr::null_mut(), buf);
1✔
1033
        let ok_state = PgsqlStateProgress::SSLRequestReceived;
1✔
1034

1✔
1035
        assert_eq!(state.state_progress, ok_state);
1✔
1036

1037
        // TODO add test for startup request
1038
    }
1✔
1039

1040
    #[test]
1041
    fn test_incomplete_request() {
1✔
1042
        let mut state = PgsqlState::new();
1✔
1043
        // An SSL Request
1✔
1044
        let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f];
1✔
1045

1✔
1046
        // We can pass null here as the only place that uses flow in the parse_request fn isn't run for unittests
1✔
1047
        let r = state.parse_request(std::ptr::null_mut(), &buf[0..0]);
1✔
1048
        assert_eq!(
1✔
1049
            r,
1✔
1050
            AppLayerResult {
1✔
1051
                status: 0,
1✔
1052
                consumed: 0,
1✔
1053
                needed: 0
1✔
1054
            }
1✔
1055
        );
1✔
1056

1057
        let r = state.parse_request(std::ptr::null_mut(), &buf[0..1]);
1✔
1058
        assert_eq!(
1✔
1059
            r,
1✔
1060
            AppLayerResult {
1✔
1061
                status: 1,
1✔
1062
                consumed: 0,
1✔
1063
                needed: 2
1✔
1064
            }
1✔
1065
        );
1✔
1066

1067
        let r = state.parse_request(std::ptr::null_mut(), &buf[0..2]);
1✔
1068
        assert_eq!(
1✔
1069
            r,
1✔
1070
            AppLayerResult {
1✔
1071
                status: 1,
1✔
1072
                consumed: 0,
1✔
1073
                needed: 3
1✔
1074
            }
1✔
1075
        );
1✔
1076
    }
1✔
1077

1078
    #[test]
1079
    fn test_find_or_create_tx() {
1✔
1080
        let mut state = PgsqlState::new();
1✔
1081
        state.state_progress = PgsqlStateProgress::UnknownState;
1✔
1082
        let tx = state.find_or_create_tx();
1✔
1083
        assert!(tx.is_none());
1✔
1084

1085
        state.state_progress = PgsqlStateProgress::IdleState;
1✔
1086
        let tx = state.find_or_create_tx();
1✔
1087
        assert!(tx.is_some());
1✔
1088

1089
        // Now, even though there isn't a new transaction created, the previous one is available
1090
        state.state_progress = PgsqlStateProgress::SSLRejectedReceived;
1✔
1091
        let tx = state.find_or_create_tx();
1✔
1092
        assert!(tx.is_some());
1✔
1093
        assert_eq!(tx.unwrap().tx_id, 1);
1✔
1094
    }
1✔
1095

1096
    #[test]
1097
    fn test_row_cnt() {
1✔
1098
        let mut tx = PgsqlTransaction::new();
1✔
1099
        assert_eq!(tx.get_row_cnt(), 0);
1✔
1100

1101
        tx.incr_row_cnt();
1✔
1102
        assert_eq!(tx.get_row_cnt(), 1);
1✔
1103
    }
1✔
1104
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc