Skip to main content

netget/client/db2/
wire.rs

1//! DRDA in the requesting role: the Db2 client's codec.
2//!
3//! Rust owns everything below the SQL: DSS framing (request chains, correlators, continuation
4//! segments), DDM objects and their extended lengths, the EBCDIC (CCSID 37) DDM character
5//! strings, the package name every SQL command names, and the FD:OCA structures replies carry
6//! (SQLCA, SQLDA, the query answer set descriptor and its rows). The model chooses statements
7//! and reads decoded results; it never sees or writes a byte of this.
8//!
9//! Every length a server declares is checked against what it sent and against a bound of
10//! ours before anything is allocated for it: a DSS and its continuations ([`MAX_DSS_BYTES`]),
11//! a whole reply chain ([`MAX_REPLY_BYTES`], [`MAX_REPLY_DSS`]), objects per DSS
12//! ([`MAX_OBJECTS_PER_DSS`]), columns ([`MAX_COLUMNS`]), rows ([`MAX_ROWS_LIMIT`]) and the
13//! bytes of one answer set ([`MAX_RESULT_BYTES`]). None of the decoders recurses.
14//!
15//! The layouts follow DRDA (The Open Group, Volume 1) at SQLAM level 7, cross-checked against
16//! Apache Derby's network server and client (`org.apache.derby.impl.drda`,
17//! `org.apache.derby.client.net`), which is also the server the tests drive.
18
19use anyhow::{anyhow, bail, ensure, Result};
20use serde_json::{json, Map, Value};
21
22use crate::server::db2::drda::{ascii_byte_to_ebcdic, ebcdic_byte_to_ascii};
23
24/// DDM code points this client sends or reads.
25pub mod cp {
26    // Commands
27    pub const EXCSAT: u16 = 0x1041;
28    pub const ACCSEC: u16 = 0x106D;
29    pub const SECCHK: u16 = 0x106E;
30    pub const ACCRDB: u16 = 0x2001;
31    pub const CLSQRY: u16 = 0x2005;
32    pub const CNTQRY: u16 = 0x2006;
33    pub const EXCSQLIMM: u16 = 0x200A;
34    pub const OPNQRY: u16 = 0x200C;
35    pub const PRPSQLSTT: u16 = 0x200D;
36    pub const RDBCMM: u16 = 0x200E;
37    pub const RDBRLLBCK: u16 = 0x200F;
38    // Command data and reply data objects
39    pub const SQLSTT: u16 = 0x2414;
40    pub const SQLCARD: u16 = 0x2408;
41    pub const SQLDARD: u16 = 0x2411;
42    pub const QRYDSC: u16 = 0x241A;
43    pub const QRYDTA: u16 = 0x241B;
44    pub const EXTDTA: u16 = 0x146C;
45    pub const EXCSATRD: u16 = 0x1443;
46    pub const ACCSECRD: u16 = 0x14AC;
47    pub const TYPDEFNAM: u16 = 0x002F;
48    pub const TYPDEFOVR: u16 = 0x0035;
49    /// Derby's piggy-backed session data: isolation level and current schema.
50    pub const PBSD: u16 = 0xC000;
51    // Reply messages
52    pub const SECCHKRM: u16 = 0x1219;
53    pub const ACCRDBRM: u16 = 0x2201;
54    pub const MGRLVLRM: u16 = 0x1210;
55    pub const AGNPRMRM: u16 = 0x1232;
56    pub const PRCCNVRM: u16 = 0x1245;
57    pub const SYNTAXRM: u16 = 0x124C;
58    pub const CMDNSPRM: u16 = 0x1250;
59    pub const PRMNSPRM: u16 = 0x1251;
60    pub const VALNSPRM: u16 = 0x1252;
61    pub const OBJNSPRM: u16 = 0x1253;
62    pub const CMDCHKRM: u16 = 0x1254;
63    pub const QRYNOPRM: u16 = 0x2202;
64    pub const RDBNACRM: u16 = 0x2204;
65    pub const OPNQRYRM: u16 = 0x2205;
66    pub const ENDQRYRM: u16 = 0x220B;
67    pub const ENDUOWRM: u16 = 0x220C;
68    pub const ABNUOWRM: u16 = 0x220D;
69    pub const DTAMCHRM: u16 = 0x220E;
70    pub const QRYPOPRM: u16 = 0x220F;
71    pub const RDBNFNRM: u16 = 0x2211;
72    pub const OPNQFLRM: u16 = 0x2212;
73    pub const SQLERRRM: u16 = 0x2213;
74    pub const RDBUPDRM: u16 = 0x2218;
75    pub const RSLSETRM: u16 = 0x2219;
76    pub const RDBAFLRM: u16 = 0x221A;
77    pub const CMMRQSRM: u16 = 0x2225;
78    pub const RDBATHRM: u16 = 0x22CB;
79    // Parameters
80    pub const EXTNAM: u16 = 0x115E;
81    pub const SRVCLSNM: u16 = 0x1147;
82    pub const SRVNAM: u16 = 0x116D;
83    pub const SRVRLSLV: u16 = 0x115A;
84    pub const MGRLVLLS: u16 = 0x1404;
85    pub const SECMEC: u16 = 0x11A2;
86    pub const SECCHKCD: u16 = 0x11A4;
87    pub const SVRCOD: u16 = 0x1149;
88    pub const SYNERRCD: u16 = 0x114A;
89    pub const CODPNT: u16 = 0x000C;
90    pub const RDBNAM: u16 = 0x2110;
91    pub const USRID: u16 = 0x11A0;
92    pub const PASSWORD: u16 = 0x11A1;
93    pub const PRDID: u16 = 0x112E;
94    pub const RDBACCCL: u16 = 0x210F;
95    pub const CRRTKN: u16 = 0x2135;
96    pub const CCSIDSBC: u16 = 0x119C;
97    pub const CCSIDDBC: u16 = 0x119D;
98    pub const CCSIDMBC: u16 = 0x119E;
99    pub const PKGNAMCSN: u16 = 0x2113;
100    pub const RDBCMTOK: u16 = 0x2105;
101    pub const RTNSQLDA: u16 = 0x2116;
102    pub const TYPSQLDA: u16 = 0x2146;
103    pub const QRYBLKSZ: u16 = 0x2114;
104    pub const QRYCLSIMP: u16 = 0x215D;
105    pub const QRYINSID: u16 = 0x215B;
106    pub const UOWDSP: u16 = 0x2115;
107    // Managers named in MGRLVLLS
108    pub const AGENT: u16 = 0x1403;
109    pub const SECMGR: u16 = 0x1440;
110    pub const SQLAM: u16 = 0x2407;
111    pub const RDB: u16 = 0x240F;
112}
113
114/// DSS types (low nibble of the format byte).
115pub const DSS_RQSDSS: u8 = 0x01;
116pub const DSS_RPYDSS: u8 = 0x02;
117pub const DSS_OBJDSS: u8 = 0x03;
118/// Format flags: another DSS follows in this chain / it carries the same correlator.
119const DSS_CHAINED: u8 = 0x40;
120const DSS_SAME_CORRELATOR: u8 = 0x10;
121/// The largest DSS segment the 15-bit length can describe.
122const MAX_SEGMENT: usize = 0x7FFF;
123
124/// SQLAM level whose FD:OCA layouts this codec reads. A server below it is refused.
125pub const SQLAM_LEVEL: u16 = 7;
126/// USRIDPWD: user id and password, sent in the clear.
127pub const SECMEC_USRIDPWD: u16 = 3;
128/// USRIDONL: user id only, for a connection with no password.
129pub const SECMEC_USRIDONL: u16 = 4;
130/// The data representation this client declares for what it sends: big-endian integers,
131/// and through TYPDEFOVR, UTF-8 single-byte and mixed characters.
132pub const OUR_TYPDEFNAM: &str = "QTDSQLASC";
133pub const CCSID_UTF8: u16 = 1208;
134pub const CCSID_UTF16: u16 = 1200;
135/// The dynamic-SQL package every statement names: collection NULLID, package SYSLH200 (large,
136/// with hold, isolation 2 = cursor stability), consistency token SYSLVL01, section 1. These are
137/// the names IBM's JDBC driver binds; Derby reads isolation and holdability out of them.
138pub const PKG_COLLECTION: &str = "NULLID";
139pub const PKG_ID: &str = "SYSLH200";
140pub const PKG_TOKEN: &[u8; 8] = b"SYSLVL01";
141pub const PKG_SECTION: u16 = 1;
142
143/// One reassembled DSS (all its continuation segments joined) may be at most this large.
144pub const MAX_DSS_BYTES: usize = 8 * 1024 * 1024;
145/// One reply chain — every DSS answering one command — may be at most this large.
146pub const MAX_REPLY_BYTES: usize = 16 * 1024 * 1024;
147/// One reply chain may hold at most this many DSSes.
148pub const MAX_REPLY_DSS: usize = 4096;
149/// One DSS may hold at most this many DDM objects.
150pub const MAX_OBJECTS_PER_DSS: usize = 256;
151/// A described result may have at most this many columns (Db2's own limit is 1012).
152pub const MAX_COLUMNS: usize = 1024;
153/// The most rows one `execute_query` may deliver, whatever `max_rows` asks for.
154pub const MAX_ROWS_LIMIT: usize = 1000;
155/// Rows delivered when `max_rows` is not given.
156pub const DEFAULT_MAX_ROWS: usize = 100;
157/// The query block size asked for in OPNQRY and CNTQRY.
158pub const QUERY_BLOCK_SIZE: u32 = 32767;
159/// The answer-set bytes (QRYDTA) one query may accumulate before it is refused.
160pub const MAX_RESULT_BYTES: usize = 8 * 1024 * 1024;
161/// CNTQRY round trips one query may take.
162pub const MAX_CONTINUES: usize = 512;
163/// The longest statement sent (Db2's own statement limit is 2 MiB).
164pub const MAX_SQL_BYTES: usize = 2 * 1024 * 1024;
165/// User id and password are DDM strings of at most 255 bytes.
166pub const MAX_CREDENTIAL_BYTES: usize = 255;
167/// A relational database name, with any connection attributes, is at most 255 bytes here.
168pub const MAX_RDBNAM_BYTES: usize = 255;
169/// RDBNAM, RDB collection and package id are padded with blanks to 18 bytes.
170const NAME_PAD: usize = 18;
171/// Character fields in an answer row: the longest fixed field this client will copy.
172const MAX_FIXED_FIELD: usize = 32767;
173/// Packed decimal: DRDA allows at most 31 digits.
174const MAX_DECIMAL_PRECISION: usize = 31;
175
176// ============================================================================
177// DDM character strings (CCSID 37)
178// ============================================================================
179
180/// Encode a DDM character string in CCSID 37, refusing any character it cannot carry rather
181/// than substituting one: a user id or password that arrived altered would be a different
182/// credential.
183pub fn ebcdic(s: &str, what: &str) -> Result<Vec<u8>> {
184    s.chars()
185        .map(|c| {
186            let b = u8::try_from(c as u32)
187                .ok()
188                .filter(|b| (0x20..0x7F).contains(b));
189            match b {
190                Some(b) => Ok(ascii_byte_to_ebcdic(b)),
191                None => bail!(
192                    "{what} holds {c:?}, which DDM's EBCDIC strings (CCSID 37) cannot carry; \
193                     only printable ASCII is sent"
194                ),
195            }
196        })
197        .collect()
198}
199
200/// Decode a DDM character string from CCSID 37, trailing blanks dropped.
201pub fn from_ebcdic(bytes: &[u8]) -> String {
202    let s: String = bytes
203        .iter()
204        .map(|&b| ebcdic_byte_to_ascii(b) as char)
205        .collect();
206    s.trim_end().to_string()
207}
208
209fn padded(mut v: Vec<u8>, len: usize) -> Vec<u8> {
210    while v.len() < len {
211        v.push(0x40); // EBCDIC blank
212    }
213    v
214}
215
216// ============================================================================
217// Encoding requests
218// ============================================================================
219
220/// `length | codepoint | data`, with an extended length when `data` does not fit 15 bits.
221pub fn object(codepoint: u16, data: &[u8]) -> Vec<u8> {
222    let mut out = Vec::with_capacity(data.len() + 8);
223    if data.len() + 4 <= MAX_SEGMENT {
224        out.extend_from_slice(&((data.len() + 4) as u16).to_be_bytes());
225        out.extend_from_slice(&codepoint.to_be_bytes());
226    } else {
227        // 0x8008: the high bit says "extended", and 8 - 4 = four bytes of length follow the
228        // code point, counting the data only.
229        out.extend_from_slice(&0x8008u16.to_be_bytes());
230        out.extend_from_slice(&codepoint.to_be_bytes());
231        out.extend_from_slice(&(data.len() as u32).to_be_bytes());
232    }
233    out.extend_from_slice(data);
234    out
235}
236
237/// Several parameters inside one object.
238fn collection(codepoint: u16, params: &[Vec<u8>]) -> Vec<u8> {
239    object(codepoint, &params.concat())
240}
241
242/// Wrap `payload` in a DSS, splitting it into continuation segments when it is longer than one
243/// segment can say.
244pub fn dss(
245    dss_type: u8,
246    correlator: u16,
247    chained: bool,
248    same_correlator: bool,
249    payload: &[u8],
250) -> Vec<u8> {
251    let mut format = dss_type & 0x0F;
252    if chained {
253        format |= DSS_CHAINED;
254        if same_correlator {
255            format |= DSS_SAME_CORRELATOR;
256        }
257    }
258    let mut out = Vec::with_capacity(payload.len() + 16);
259    let first = payload.len().min(MAX_SEGMENT - 6);
260    let continued = first < payload.len();
261    let len = if continued {
262        0xFFFF
263    } else {
264        (first + 6) as u16
265    };
266    out.extend_from_slice(&len.to_be_bytes());
267    out.push(0xD0);
268    out.push(format);
269    out.extend_from_slice(&correlator.to_be_bytes());
270    out.extend_from_slice(&payload[..first]);
271    let mut rest = &payload[first..];
272    while !rest.is_empty() {
273        let take = rest.len().min(MAX_SEGMENT - 2);
274        let more = take < rest.len();
275        let header = if more { 0xFFFF } else { (take + 2) as u16 };
276        out.extend_from_slice(&header.to_be_bytes());
277        out.extend_from_slice(&rest[..take]);
278        rest = &rest[take..];
279    }
280    out
281}
282
283/// One command: its DSSes, all under one correlator, chained to each other.
284pub fn command(correlator: u16, parts: &[(u8, Vec<u8>)]) -> Vec<u8> {
285    let mut out = Vec::new();
286    for (i, (dss_type, payload)) in parts.iter().enumerate() {
287        let last = i + 1 == parts.len();
288        out.extend(dss(*dss_type, correlator, !last, !last, payload));
289    }
290    out
291}
292
293/// The package, consistency token and section every SQL command names. When the database name
294/// is longer than 18 bytes the extended form (each name preceded by its length) is used.
295#[derive(Clone, Debug)]
296pub struct Package {
297    rdbnam: Vec<u8>,
298}
299
300impl Package {
301    pub fn new(rdbnam: &str) -> Result<Self> {
302        Ok(Self {
303            rdbnam: rdb_name(rdbnam)?,
304        })
305    }
306
307    pub fn encode(&self) -> Vec<u8> {
308        let collection = padded(
309            ebcdic(PKG_COLLECTION, "collection").unwrap_or_default(),
310            NAME_PAD,
311        );
312        let package = padded(ebcdic(PKG_ID, "package").unwrap_or_default(), NAME_PAD);
313        let mut data = Vec::with_capacity(80);
314        if self.rdbnam.len() == NAME_PAD {
315            data.extend_from_slice(&self.rdbnam);
316            data.extend_from_slice(&collection);
317            data.extend_from_slice(&package);
318        } else {
319            for part in [&self.rdbnam, &collection, &package] {
320                data.extend_from_slice(&(part.len() as u16).to_be_bytes());
321                data.extend_from_slice(part);
322            }
323        }
324        data.extend_from_slice(&ebcdic_bytes(PKG_TOKEN));
325        data.extend_from_slice(&PKG_SECTION.to_be_bytes());
326        object(cp::PKGNAMCSN, &data)
327    }
328}
329
330fn ebcdic_bytes(b: &[u8]) -> Vec<u8> {
331    b.iter().map(|&c| ascii_byte_to_ebcdic(c)).collect()
332}
333
334/// RDBNAM as DDM wants it: CCSID 37, at least 18 bytes (blank-padded), at most
335/// [`MAX_RDBNAM_BYTES`].
336pub fn rdb_name(name: &str) -> Result<Vec<u8>> {
337    ensure!(!name.trim().is_empty(), "the database name is empty");
338    ensure!(
339        name.len() <= MAX_RDBNAM_BYTES,
340        "the database name is {} bytes; at most {MAX_RDBNAM_BYTES} are sent",
341        name.len()
342    );
343    Ok(padded(ebcdic(name, "the database name")?, NAME_PAD))
344}
345
346fn credential(value: &str, what: &str) -> Result<Vec<u8>> {
347    ensure!(
348        value.len() <= MAX_CREDENTIAL_BYTES,
349        "{what} is {} bytes; DDM carries at most {MAX_CREDENTIAL_BYTES}",
350        value.len()
351    );
352    ebcdic(value, what)
353}
354
355/// EXCSAT: who we are and the manager levels we speak.
356pub fn excsat(product_id: &str) -> Result<Vec<u8>> {
357    let mut mgr = Vec::new();
358    for (m, level) in [
359        (cp::AGENT, 7u16),
360        (cp::SQLAM, SQLAM_LEVEL),
361        (cp::RDB, 7),
362        (cp::SECMGR, 7),
363    ] {
364        mgr.extend_from_slice(&m.to_be_bytes());
365        mgr.extend_from_slice(&level.to_be_bytes());
366    }
367    Ok(collection(
368        cp::EXCSAT,
369        &[
370            object(cp::EXTNAM, &ebcdic("netget-db2-client", "EXTNAM")?),
371            object(cp::SRVNAM, &ebcdic("netget", "SRVNAM")?),
372            object(cp::SRVRLSLV, &ebcdic(product_id, "product_id")?),
373            object(cp::MGRLVLLS, &mgr),
374            object(cp::SRVCLSNM, &ebcdic("QNETGET/RUST", "SRVCLSNM")?),
375        ],
376    ))
377}
378
379pub fn accsec(secmec: u16, rdbnam: &str) -> Result<Vec<u8>> {
380    Ok(collection(
381        cp::ACCSEC,
382        &[
383            object(cp::SECMEC, &secmec.to_be_bytes()),
384            object(cp::RDBNAM, &rdb_name(rdbnam)?),
385        ],
386    ))
387}
388
389pub fn secchk(secmec: u16, rdbnam: &str, user: &str, password: Option<&str>) -> Result<Vec<u8>> {
390    ensure!(!user.is_empty(), "a user id is required");
391    let mut params = vec![
392        object(cp::SECMEC, &secmec.to_be_bytes()),
393        object(cp::RDBNAM, &rdb_name(rdbnam)?),
394        object(cp::USRID, &credential(user, "the user id")?),
395    ];
396    if let Some(p) = password {
397        params.push(object(cp::PASSWORD, &credential(p, "the password")?));
398    }
399    Ok(collection(cp::SECCHK, &params))
400}
401
402/// ACCRDB: open the database, declaring our data representation and product.
403pub fn accrdb(rdbnam: &str, product_id: &str, crrtkn: &[u8]) -> Result<Vec<u8>> {
404    ensure!(
405        product_id.len() == 8 && product_id.is_ascii(),
406        "product_id must be 8 ASCII characters (pppvvrrm), got {product_id:?}"
407    );
408    let ccsid = |c: u16, v: u16| object(c, &v.to_be_bytes());
409    let ovr = [
410        ccsid(cp::CCSIDSBC, CCSID_UTF8),
411        ccsid(cp::CCSIDDBC, CCSID_UTF16),
412        ccsid(cp::CCSIDMBC, CCSID_UTF8),
413    ]
414    .concat();
415    Ok(collection(
416        cp::ACCRDB,
417        &[
418            object(cp::RDBNAM, &rdb_name(rdbnam)?),
419            object(cp::RDBACCCL, &cp::SQLAM.to_be_bytes()),
420            object(cp::PRDID, &ebcdic(product_id, "product_id")?),
421            object(cp::TYPDEFNAM, &ebcdic(OUR_TYPDEFNAM, "TYPDEFNAM")?),
422            object(cp::CRRTKN, crrtkn),
423            object(cp::TYPDEFOVR, &ovr),
424        ],
425    ))
426}
427
428/// The correlation token DRDA asks a TCP/IP requester for: its IPv4 address as eight hex
429/// digits whose first is shifted into G..P, a dot, its port as four hex digits, then six
430/// bytes that make this connection's token unique.
431pub fn correlation_token(local: std::net::SocketAddr, unique: [u8; 6]) -> Vec<u8> {
432    let ip = match local.ip() {
433        std::net::IpAddr::V4(v4) => u32::from(v4),
434        std::net::IpAddr::V6(v6) => v6.to_ipv4_mapped().map(u32::from).unwrap_or(0x7F00_0001),
435    };
436    let mut text = format!("{ip:08X}");
437    let first = text.as_bytes()[0];
438    let shifted =
439        (b'G' + u8::from_str_radix(&(first as char).to_string(), 16).unwrap_or(0)) as char;
440    text.replace_range(0..1, &shifted.to_string());
441    text.push('.');
442    text.push_str(&format!("{:04X}", local.port()));
443    let mut out = ebcdic_bytes(text.as_bytes());
444    out.extend_from_slice(&unique);
445    out
446}
447
448/// SQLSTT: a nullable mixed-character LOB (null indicator, 4-byte length, UTF-8 text) then a
449/// null single-byte string.
450pub fn sqlstt(sql: &str) -> Result<Vec<u8>> {
451    ensure!(!sql.trim().is_empty(), "the statement is empty");
452    ensure!(
453        sql.len() <= MAX_SQL_BYTES,
454        "the statement is {} bytes; at most {MAX_SQL_BYTES} are sent",
455        sql.len()
456    );
457    let mut data = Vec::with_capacity(sql.len() + 6);
458    data.push(0x00);
459    data.extend_from_slice(&(sql.len() as u32).to_be_bytes());
460    data.extend_from_slice(sql.as_bytes());
461    data.push(0xFF);
462    Ok(object(cp::SQLSTT, &data))
463}
464
465/// EXCSQLIMM + SQLSTT: run a statement that returns no rows.
466pub fn excsqlimm(pkg: &Package, sql: &str) -> Result<Vec<(u8, Vec<u8>)>> {
467    Ok(vec![
468        (
469            DSS_RQSDSS,
470            collection(
471                cp::EXCSQLIMM,
472                &[pkg.encode(), object(cp::RDBCMTOK, &[0xF1])],
473            ),
474        ),
475        (DSS_OBJDSS, sqlstt(sql)?),
476    ])
477}
478
479/// PRPSQLSTT + SQLSTT: prepare a query and have its output described (extended SQLDA).
480pub fn prpsqlstt(pkg: &Package, sql: &str) -> Result<Vec<(u8, Vec<u8>)>> {
481    Ok(vec![
482        (
483            DSS_RQSDSS,
484            collection(
485                cp::PRPSQLSTT,
486                &[
487                    pkg.encode(),
488                    object(cp::RTNSQLDA, &[0xF1]),
489                    object(cp::TYPSQLDA, &[0x04]),
490                ],
491            ),
492        ),
493        (DSS_OBJDSS, sqlstt(sql)?),
494    ])
495}
496
497/// OPNQRY: open the prepared query; the server closes it itself at end of data.
498pub fn opnqry(pkg: &Package) -> Vec<(u8, Vec<u8>)> {
499    vec![(
500        DSS_RQSDSS,
501        collection(
502            cp::OPNQRY,
503            &[
504                pkg.encode(),
505                object(cp::QRYBLKSZ, &QUERY_BLOCK_SIZE.to_be_bytes()),
506                object(cp::QRYCLSIMP, &[0x01]),
507            ],
508        ),
509    )]
510}
511
512pub fn cntqry(pkg: &Package, qryinsid: &[u8]) -> Vec<(u8, Vec<u8>)> {
513    vec![(
514        DSS_RQSDSS,
515        collection(
516            cp::CNTQRY,
517            &[
518                pkg.encode(),
519                object(cp::QRYBLKSZ, &QUERY_BLOCK_SIZE.to_be_bytes()),
520                object(cp::QRYINSID, qryinsid),
521            ],
522        ),
523    )]
524}
525
526pub fn clsqry(pkg: &Package, qryinsid: &[u8]) -> Vec<(u8, Vec<u8>)> {
527    vec![(
528        DSS_RQSDSS,
529        collection(cp::CLSQRY, &[pkg.encode(), object(cp::QRYINSID, qryinsid)]),
530    )]
531}
532
533pub fn rdbcmm() -> Vec<(u8, Vec<u8>)> {
534    vec![(DSS_RQSDSS, object(cp::RDBCMM, &[]))]
535}
536
537pub fn rdbrllbck() -> Vec<(u8, Vec<u8>)> {
538    vec![(DSS_RQSDSS, object(cp::RDBRLLBCK, &[]))]
539}
540
541// ============================================================================
542// Reading replies: DSS, objects, parameters
543// ============================================================================
544
545/// One reply DSS with its continuation segments joined.
546#[derive(Debug, Clone, PartialEq, Eq)]
547pub struct Dss {
548    pub dss_type: u8,
549    pub chained: bool,
550    pub correlator: u16,
551    pub payload: Vec<u8>,
552}
553
554/// Take one complete DSS off the front of `buf`. `Ok(None)` means more bytes are needed; an
555/// error means the stream is not DRDA, or declares more than [`MAX_DSS_BYTES`].
556pub fn take_dss(buf: &mut Vec<u8>) -> Result<Option<Dss>> {
557    if buf.len() < 6 {
558        return Ok(None);
559    }
560    let raw = u16::from_be_bytes([buf[0], buf[1]]);
561    ensure!(
562        buf[2] == 0xD0,
563        "not a DSS: byte 2 is 0x{:02X}, where DRDA puts 0xD0",
564        buf[2]
565    );
566    let format = buf[3];
567    let correlator = u16::from_be_bytes([buf[4], buf[5]]);
568    let mut more = raw & 0x8000 != 0;
569    let first = if more { MAX_SEGMENT } else { raw as usize };
570    ensure!(
571        first >= 10,
572        "a DSS declares {first} bytes, fewer than its own headers"
573    );
574    // Walk the segment headers that have arrived, refusing a chain that declares too much
575    // before waiting for any of it.
576    let mut ranges = vec![(6usize, first)];
577    let mut pos = first;
578    let mut total = first - 6;
579    while more {
580        if buf.len() < pos + 2 {
581            return Ok(None);
582        }
583        let header = u16::from_be_bytes([buf[pos], buf[pos + 1]]);
584        more = header & 0x8000 != 0;
585        let len = if more { MAX_SEGMENT } else { header as usize };
586        ensure!(len > 2, "a DSS continuation declares {len} bytes");
587        total += len - 2;
588        ensure!(
589            total <= MAX_DSS_BYTES,
590            "a DSS continues past {MAX_DSS_BYTES} bytes; refused"
591        );
592        ranges.push((pos + 2, pos + len));
593        pos += len;
594    }
595    if buf.len() < pos {
596        return Ok(None);
597    }
598    let mut payload = Vec::with_capacity(total);
599    for (start, end) in ranges {
600        payload.extend_from_slice(&buf[start..end]);
601    }
602    buf.drain(..pos);
603    Ok(Some(Dss {
604        dss_type: format & 0x0F,
605        chained: format & DSS_CHAINED != 0,
606        correlator,
607        payload,
608    }))
609}
610
611/// One DDM object.
612#[derive(Debug, Clone, PartialEq, Eq)]
613pub struct Object {
614    pub codepoint: u16,
615    pub data: Vec<u8>,
616}
617
618/// The objects in one DSS, in order. Extended lengths are honoured; a length running past the
619/// DSS is an error.
620pub fn objects(payload: &[u8]) -> Result<Vec<Object>> {
621    let mut out = Vec::new();
622    let mut pos = 0;
623    while pos < payload.len() {
624        ensure!(
625            out.len() < MAX_OBJECTS_PER_DSS,
626            "a DSS holds more than {MAX_OBJECTS_PER_DSS} objects; refused"
627        );
628        let (codepoint, data, used) = one_object(&payload[pos..])?;
629        out.push(Object {
630            codepoint,
631            data: data.to_vec(),
632        });
633        pos += used;
634    }
635    Ok(out)
636}
637
638/// `(codepoint, data, bytes used)` for the object at the front of `b`.
639fn one_object(b: &[u8]) -> Result<(u16, &[u8], usize)> {
640    ensure!(
641        b.len() >= 4,
642        "a DDM object is cut short ({} bytes)",
643        b.len()
644    );
645    let ll = u16::from_be_bytes([b[0], b[1]]);
646    let codepoint = u16::from_be_bytes([b[2], b[3]]);
647    if ll & 0x8000 == 0 {
648        let len = ll as usize;
649        ensure!(
650            len >= 4 && len <= b.len(),
651            "DDM object 0x{codepoint:04X} declares {len} bytes where {} remain",
652            b.len()
653        );
654        return Ok((codepoint, &b[4..len], len));
655    }
656    let ext = (ll & 0x7FFF) as usize;
657    ensure!(
658        matches!(ext, 4 | 8 | 10 | 12),
659        "DDM object 0x{codepoint:04X} has an extended length field of {ext} bytes"
660    );
661    let n = ext - 4;
662    if n == 0 {
663        // Layer-B streaming: the object runs to the end of the DSS.
664        return Ok((codepoint, &b[4..], b.len()));
665    }
666    ensure!(
667        b.len() >= 4 + n,
668        "DDM object 0x{codepoint:04X} is cut short"
669    );
670    let mut len: u64 = 0;
671    for &byte in &b[4..4 + n] {
672        len = (len << 8) | byte as u64;
673    }
674    let end = (4 + n) as u64 + len;
675    ensure!(
676        end <= b.len() as u64,
677        "DDM object 0x{codepoint:04X} declares {len} bytes where {} remain",
678        b.len() - 4 - n
679    );
680    Ok((codepoint, &b[4 + n..end as usize], end as usize))
681}
682
683/// The parameters inside an object's data.
684pub fn params(data: &[u8]) -> Result<Vec<(u16, &[u8])>> {
685    let mut out = Vec::new();
686    let mut pos = 0;
687    while pos < data.len() {
688        ensure!(
689            out.len() < MAX_OBJECTS_PER_DSS,
690            "an object holds more than {MAX_OBJECTS_PER_DSS} parameters; refused"
691        );
692        let (cp, value, used) = one_object(&data[pos..])?;
693        out.push((cp, value));
694        pos += used;
695    }
696    Ok(out)
697}
698
699pub fn param(data: &[u8], codepoint: u16) -> Result<Option<&[u8]>> {
700    Ok(params(data)?
701        .into_iter()
702        .find(|(cp, _)| *cp == codepoint)
703        .map(|(_, v)| v))
704}
705
706/// SVRCOD of a reply message, 0 when absent.
707pub fn severity(data: &[u8]) -> Result<u16> {
708    Ok(match param(data, cp::SVRCOD)? {
709        Some([a, b]) => u16::from_be_bytes([*a, *b]),
710        Some(other) => bail!("SVRCOD is {} bytes, not 2", other.len()),
711        None => 0,
712    })
713}
714
715/// The name of a reply message or reply data object, for errors and logs.
716pub fn name_of(codepoint: u16) -> String {
717    let n = match codepoint {
718        cp::SECCHKRM => "SECCHKRM (security check)",
719        cp::ACCRDBRM => "ACCRDBRM (access RDB)",
720        cp::MGRLVLRM => "MGRLVLRM (manager level conflict)",
721        cp::AGNPRMRM => "AGNPRMRM (permanent agent error)",
722        cp::PRCCNVRM => "PRCCNVRM (conversational protocol error)",
723        cp::SYNTAXRM => "SYNTAXRM (data stream syntax error)",
724        cp::CMDNSPRM => "CMDNSPRM (command not supported)",
725        cp::PRMNSPRM => "PRMNSPRM (parameter not supported)",
726        cp::VALNSPRM => "VALNSPRM (parameter value not supported)",
727        cp::OBJNSPRM => "OBJNSPRM (object not supported)",
728        cp::CMDCHKRM => "CMDCHKRM (command check)",
729        cp::QRYNOPRM => "QRYNOPRM (query not open)",
730        cp::RDBNACRM => "RDBNACRM (RDB not accessed)",
731        cp::OPNQRYRM => "OPNQRYRM (open query complete)",
732        cp::ENDQRYRM => "ENDQRYRM (end of query)",
733        cp::ENDUOWRM => "ENDUOWRM (end of unit of work)",
734        cp::ABNUOWRM => "ABNUOWRM (abnormal end of unit of work)",
735        cp::DTAMCHRM => "DTAMCHRM (data descriptor mismatch)",
736        cp::QRYPOPRM => "QRYPOPRM (query previously opened)",
737        cp::RDBNFNRM => "RDBNFNRM (RDB not found)",
738        cp::OPNQFLRM => "OPNQFLRM (open query failure)",
739        cp::SQLERRRM => "SQLERRRM (SQL error condition)",
740        cp::RDBUPDRM => "RDBUPDRM (RDB updated)",
741        cp::RSLSETRM => "RSLSETRM (result set)",
742        cp::RDBAFLRM => "RDBAFLRM (RDB access failed)",
743        cp::CMMRQSRM => "CMMRQSRM (commitment request)",
744        cp::RDBATHRM => "RDBATHRM (not authorized to RDB)",
745        cp::SQLCARD => "SQLCARD",
746        cp::SQLDARD => "SQLDARD",
747        cp::QRYDSC => "QRYDSC",
748        cp::QRYDTA => "QRYDTA",
749        cp::EXTDTA => "EXTDTA",
750        _ => return format!("0x{codepoint:04X}"),
751    };
752    n.to_string()
753}
754
755/// What SECCHKCD says, in words.
756pub fn secchkcd_meaning(code: u8) -> &'static str {
757    match code {
758        0x00 => "accepted",
759        0x01 => "security mechanism not supported",
760        0x0E => "password expired",
761        0x0F => "password invalid",
762        0x10 => "password missing",
763        0x12 => "user id missing",
764        0x13 => "user id invalid",
765        0x14 => "user id revoked",
766        0x15 => "new password invalid",
767        0x0A => "local security service non-retryable error",
768        0x0B => "SECTKN missing or invalid",
769        _ => "refused",
770    }
771}
772
773// ============================================================================
774// The server's data representation
775// ============================================================================
776
777#[derive(Clone, Copy, Debug, PartialEq, Eq)]
778pub enum Endian {
779    Big,
780    Little,
781}
782
783/// How the server represents FD:OCA data: byte order from its TYPDEFNAM, character sets from
784/// its TYPDEFOVR.
785#[derive(Clone, Debug, PartialEq, Eq)]
786pub struct Repr {
787    pub typdefnam: String,
788    pub endian: Endian,
789    /// S/390 hexadecimal floating point: not decoded.
790    pub hex_float: bool,
791    pub sbc: u16,
792    pub mbc: u16,
793    pub dbc: u16,
794}
795
796impl Default for Repr {
797    fn default() -> Self {
798        Self::named(OUR_TYPDEFNAM)
799    }
800}
801
802impl Repr {
803    pub fn named(typdefnam: &str) -> Self {
804        let endian = match typdefnam {
805            "QTDSQLX86" | "QTDSQLVAX" => Endian::Little,
806            _ => Endian::Big,
807        };
808        Self {
809            typdefnam: typdefnam.to_string(),
810            endian,
811            hex_float: typdefnam == "QTDSQL370",
812            sbc: CCSID_UTF8,
813            mbc: CCSID_UTF8,
814            dbc: CCSID_UTF16,
815        }
816    }
817
818    /// Apply a TYPDEFNAM object (its value in DDM characters).
819    pub fn set_name(&mut self, raw: &[u8]) -> Result<()> {
820        let mut name = from_ebcdic(raw);
821        if !name.starts_with("QTDSQL") && raw.is_ascii() {
822            // A server that negotiated nothing still sometimes answers in ASCII.
823            name = String::from_utf8_lossy(raw).trim_end().to_string();
824        }
825        ensure!(
826            matches!(
827                name.as_str(),
828                "QTDSQL370" | "QTDSQL400" | "QTDSQLX86" | "QTDSQLASC" | "QTDSQLVAX" | "QTDSQLJVM"
829            ),
830            "the server names data representation {name:?}, which this client does not know"
831        );
832        let keep = (self.sbc, self.mbc, self.dbc);
833        *self = Self::named(&name);
834        (self.sbc, self.mbc, self.dbc) = keep;
835        Ok(())
836    }
837
838    /// Apply a TYPDEFOVR object.
839    pub fn set_overrides(&mut self, data: &[u8]) -> Result<()> {
840        for (code, value) in params(data)? {
841            let ccsid = match value {
842                [a, b] => u16::from_be_bytes([*a, *b]),
843                [0, 0, a, b] => u16::from_be_bytes([*a, *b]),
844                _ => bail!("a CCSID override is {} bytes", value.len()),
845            };
846            match code {
847                cp::CCSIDSBC => self.sbc = ccsid,
848                cp::CCSIDMBC => self.mbc = ccsid,
849                cp::CCSIDDBC => self.dbc = ccsid,
850                _ => {}
851            }
852        }
853        Ok(())
854    }
855}
856
857/// Decode character data in `ccsid`.
858pub fn decode_text(bytes: &[u8], ccsid: u16) -> Result<String> {
859    Ok(match ccsid {
860        0 | CCSID_UTF8 => String::from_utf8_lossy(bytes).into_owned(),
861        1200 | 13488 | 17584 => {
862            ensure!(bytes.len().is_multiple_of(2), "UTF-16 data of odd length");
863            let units: Vec<u16> = bytes
864                .chunks_exact(2)
865                .map(|c| u16::from_be_bytes([c[0], c[1]]))
866                .collect();
867            String::from_utf16_lossy(&units)
868        }
869        37 | 500 | 1047 | 1140 => bytes
870            .iter()
871            .map(|&b| ebcdic_byte_to_ascii(b) as char)
872            .collect(),
873        367 | 437 | 819 | 850 | 923 | 1252 => bytes.iter().map(|&b| b as char).collect(),
874        other => bail!("character data in CCSID {other}, which this client does not decode"),
875    })
876}
877
878// ============================================================================
879// FD:OCA reader
880// ============================================================================
881
882/// Why a read stopped.
883#[derive(Debug)]
884pub enum Short {
885    /// The bytes ran out: for answer-set rows, more may come with the next query block.
886    Incomplete,
887    /// The bytes are wrong.
888    Bad(anyhow::Error),
889}
890
891impl From<anyhow::Error> for Short {
892    fn from(e: anyhow::Error) -> Self {
893        Short::Bad(e)
894    }
895}
896
897type R<T> = std::result::Result<T, Short>;
898
899fn finish<T>(r: R<T>, what: &str) -> Result<T> {
900    match r {
901        Ok(v) => Ok(v),
902        Err(Short::Incomplete) => Err(anyhow!("{what} is cut short")),
903        Err(Short::Bad(e)) => Err(e),
904    }
905}
906
907struct Reader<'a> {
908    b: &'a [u8],
909    pos: usize,
910    repr: &'a Repr,
911}
912
913impl<'a> Reader<'a> {
914    fn new(b: &'a [u8], repr: &'a Repr) -> Self {
915        Self { b, pos: 0, repr }
916    }
917    fn take(&mut self, n: usize) -> R<&'a [u8]> {
918        if self.b.len() - self.pos < n {
919            return Err(Short::Incomplete);
920        }
921        let s = &self.b[self.pos..self.pos + n];
922        self.pos += n;
923        Ok(s)
924    }
925    fn u8(&mut self) -> R<u8> {
926        Ok(self.take(1)?[0])
927    }
928    fn fixed<const N: usize>(&mut self) -> R<[u8; N]> {
929        let mut a: [u8; N] = self.take(N)?.try_into().map_err(|_| Short::Incomplete)?;
930        if self.repr.endian == Endian::Little {
931            a.reverse();
932        }
933        Ok(a)
934    }
935    fn i16(&mut self) -> R<i16> {
936        Ok(i16::from_be_bytes(self.fixed()?))
937    }
938    fn u16(&mut self) -> R<u16> {
939        Ok(u16::from_be_bytes(self.fixed()?))
940    }
941    fn i32(&mut self) -> R<i32> {
942        Ok(i32::from_be_bytes(self.fixed()?))
943    }
944    fn i64(&mut self) -> R<i64> {
945        Ok(i64::from_be_bytes(self.fixed()?))
946    }
947    /// A length-prefixed string (VCS or VCM), undecoded.
948    fn vc_raw(&mut self) -> R<&'a [u8]> {
949        let n = self.u16()? as usize;
950        self.take(n)
951    }
952    /// A length-prefixed string (VCS or VCM).
953    fn vc(&mut self, ccsid: u16) -> R<String> {
954        let raw = self.vc_raw()?;
955        Ok(decode_text(raw, ccsid)?)
956    }
957    /// VCM then VCS, of which at most one is non-empty.
958    fn vcm_or_vcs(&mut self) -> R<String> {
959        let m = self.vc(self.repr.mbc)?;
960        let s = self.vc(self.repr.sbc)?;
961        Ok(if m.is_empty() { s } else { m })
962    }
963}
964
965// ============================================================================
966// SQLCA
967// ============================================================================
968
969/// An SQL communications area: what a statement did.
970#[derive(Debug, Clone, PartialEq, Eq, Default)]
971pub struct Sqlca {
972    pub sqlcode: i32,
973    pub sqlstate: String,
974    pub errproc: String,
975    pub errd: [i32; 6],
976    pub warnings: String,
977    /// SQLERRMC split on its 0xFF / 0x14 separators.
978    pub tokens: Vec<String>,
979}
980
981impl Sqlca {
982    /// Rows a statement inserted, updated or deleted (SQLERRD3).
983    pub fn row_count(&self) -> i32 {
984        self.errd[2]
985    }
986
987    pub fn to_json(&self) -> Value {
988        json!({
989            "sqlcode": self.sqlcode,
990            "sqlstate": self.sqlstate,
991            "message_tokens": self.tokens,
992        })
993    }
994}
995
996fn read_sqlca(r: &mut Reader<'_>) -> R<Option<Sqlca>> {
997    if r.u8()? & 0x80 != 0 {
998        return Ok(None);
999    }
1000    let sbc = r.repr.sbc;
1001    let sqlcode = r.i32()?;
1002    let sqlstate = decode_text(r.take(5)?, sbc)?;
1003    let errproc = decode_text(r.take(8)?, sbc)?.trim_end().to_string();
1004    let mut ca = Sqlca {
1005        sqlcode,
1006        sqlstate,
1007        errproc,
1008        ..Default::default()
1009    };
1010    if r.u8()? & 0x80 == 0 {
1011        for d in ca.errd.iter_mut() {
1012            *d = r.i32()?;
1013        }
1014        ca.warnings = decode_text(r.take(11)?, sbc)?.trim().to_string();
1015        let _rdbname = r.vc(sbc)?;
1016        // SQLERRMC: mixed, else single-byte. Its tokens are separated by 0xFF (Db2) or 0x14
1017        // (Derby), split before decoding so a separator never becomes a replacement character.
1018        let mixed = r.vc_raw()?;
1019        let single = r.vc_raw()?;
1020        let (msg, ccsid) = if mixed.is_empty() {
1021            (single, sbc)
1022        } else {
1023            (mixed, r.repr.mbc)
1024        };
1025        for token in msg.split(|b| *b == 0x14 || *b == 0xFF) {
1026            let t = crate::utils::sanitize::line_field(&decode_text(token, ccsid)?);
1027            if !t.trim().is_empty() {
1028                ca.tokens.push(t.trim().to_string());
1029            }
1030        }
1031    }
1032    if r.u8()? & 0x80 == 0 {
1033        return Err(Short::Bad(anyhow!(
1034            "the SQLCA carries a diagnostics group (SQLDIAGGRP), which this client does not \
1035             decode"
1036        )));
1037    }
1038    Ok(Some(ca))
1039}
1040
1041/// SQLCARD: one SQLCA, or none when the statement succeeded without comment.
1042pub fn parse_sqlcard(data: &[u8], repr: &Repr) -> Result<Option<Sqlca>> {
1043    let mut r = Reader::new(data, repr);
1044    let ca = finish(read_sqlca(&mut r), "the SQLCARD")?;
1045    Ok(ca)
1046}
1047
1048// ============================================================================
1049// SQLDARD: column descriptions
1050// ============================================================================
1051
1052/// One described output column.
1053#[derive(Debug, Clone, PartialEq, Eq)]
1054pub struct Column {
1055    pub name: String,
1056    pub sql_type: i16,
1057    pub nullable: bool,
1058    pub precision: i16,
1059    pub scale: i16,
1060    pub length: i64,
1061    pub ccsid: u16,
1062}
1063
1064impl Column {
1065    pub fn type_name(&self) -> String {
1066        let base = self.sql_type & !1;
1067        match base {
1068            384 => "DATE".into(),
1069            388 => "TIME".into(),
1070            392 => "TIMESTAMP".into(),
1071            404 => "BLOB".into(),
1072            408 => "CLOB".into(),
1073            412 => "DBCLOB".into(),
1074            448 => format!("VARCHAR({})", self.length),
1075            452 => format!("CHAR({})", self.length),
1076            456 => "LONG VARCHAR".into(),
1077            464 => format!("VARGRAPHIC({})", self.length),
1078            468 => format!("GRAPHIC({})", self.length),
1079            480 if self.length == 4 => "REAL".into(),
1080            480 => "DOUBLE".into(),
1081            484 => format!("DECIMAL({},{})", self.precision, self.scale),
1082            492 => "BIGINT".into(),
1083            496 => "INTEGER".into(),
1084            500 => "SMALLINT".into(),
1085            908 => format!("VARBINARY({})", self.length),
1086            912 => format!("BINARY({})", self.length),
1087            996 => "DECFLOAT".into(),
1088            2436 => "BOOLEAN".into(),
1089            other => format!("SQLTYPE {other}"),
1090        }
1091    }
1092
1093    pub fn to_json(&self) -> Value {
1094        json!({
1095            "name": self.name,
1096            "type": self.type_name(),
1097            "nullable": self.nullable,
1098        })
1099    }
1100}
1101
1102/// Derby's own SQLTYPE for a Java user-defined type, whose SQLUDTGRP it writes without a null
1103/// indicator.
1104const DERBY_UDT: i16 = 2000;
1105
1106/// SQLDARD: the statement's SQLCA, then its described columns.
1107pub fn parse_sqldard(data: &[u8], repr: &Repr) -> Result<(Option<Sqlca>, Vec<Column>)> {
1108    finish(read_sqldard(data, repr), "the SQLDARD")
1109}
1110
1111fn read_sqldard(data: &[u8], repr: &Repr) -> R<(Option<Sqlca>, Vec<Column>)> {
1112    let mut r = Reader::new(data, repr);
1113    let ca = read_sqlca(&mut r)?;
1114    // SQLDHGRP
1115    if r.u8()? & 0x80 == 0 {
1116        for _ in 0..6 {
1117            r.i16()?;
1118        }
1119        r.vc(repr.sbc)?;
1120        r.vcm_or_vcs()?;
1121    }
1122    let n = r.i16()?;
1123    if n < 0 || n as usize > MAX_COLUMNS {
1124        return Err(Short::Bad(anyhow!(
1125            "the SQLDA describes {n} columns; at most {MAX_COLUMNS} are read"
1126        )));
1127    }
1128    let mut columns = Vec::with_capacity(n as usize);
1129    for i in 0..n as usize {
1130        let precision = r.i16()?;
1131        let scale = r.i16()?;
1132        let length = r.i64()?;
1133        let sql_type = r.i16()?;
1134        // SQLCCSID is always big-endian.
1135        let c = r.take(2)?;
1136        let ccsid = u16::from_be_bytes([c[0], c[1]]);
1137        let mut name = String::new();
1138        // SQLDOPTGRP
1139        if r.u8()? & 0x80 == 0 {
1140            r.i16()?;
1141            name = r.vcm_or_vcs()?;
1142            r.vcm_or_vcs()?; // label
1143            r.vcm_or_vcs()?; // comments
1144                             // SQLUDTGRP
1145            if sql_type & !1 == DERBY_UDT {
1146                r.vcm_or_vcs()?;
1147                r.vcm_or_vcs()?;
1148            } else if r.u8()? & 0x80 == 0 {
1149                return Err(Short::Bad(anyhow!(
1150                    "column {} carries a user-defined type group, which this client does not \
1151                     decode",
1152                    i + 1
1153                )));
1154            }
1155            // SQLDXGRP
1156            if r.u8()? & 0x80 == 0 {
1157                for _ in 0..4 {
1158                    r.i16()?;
1159                }
1160                r.vc(repr.sbc)?;
1161                for _ in 0..4 {
1162                    r.vcm_or_vcs()?;
1163                }
1164            }
1165        }
1166        columns.push(Column {
1167            name: crate::utils::sanitize::line_field(&name),
1168            sql_type,
1169            nullable: sql_type & 1 == 1,
1170            precision,
1171            scale,
1172            length,
1173            ccsid,
1174        });
1175    }
1176    Ok((ca, columns))
1177}
1178
1179// ============================================================================
1180// QRYDSC: the answer-set row layout
1181// ============================================================================
1182
1183/// One field of an answer-set row: its DRDA type (odd = nullable) and length.
1184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1185pub struct Field {
1186    pub drda_type: u8,
1187    pub length: u16,
1188    /// A CCSID an SDA override gave this field.
1189    pub ccsid: Option<u16>,
1190}
1191
1192/// Parse the FD:OCA triplets of a QRYDSC (or several, concatenated) into the row layout.
1193pub fn parse_qrydsc(data: &[u8]) -> Result<Vec<Field>> {
1194    let mut fields: Vec<Field> = Vec::new();
1195    let mut overrides: Vec<(u8, u8, u16)> = Vec::new(); // (lid, drda type, ccsid)
1196    let mut mdd_protocol_type: Option<u8> = None;
1197    let mut saw_gda = false;
1198    let mut pos = 0;
1199    while pos < data.len() {
1200        ensure!(pos + 3 <= data.len(), "a QRYDSC triplet is cut short");
1201        let ll = data[pos] as usize;
1202        let kind = data[pos + 1];
1203        let id = data[pos + 2];
1204        ensure!(
1205            ll >= 3 && pos + ll <= data.len(),
1206            "a QRYDSC triplet declares {ll} bytes where {} remain",
1207            data.len() - pos
1208        );
1209        let body = &data[pos + 3..pos + ll];
1210        match kind {
1211            // MDD: which DRDA type the next SDA redefines.
1212            0x78 => {
1213                ensure!(ll == 7, "an MDD triplet is {ll} bytes, not 7");
1214                mdd_protocol_type = Some(body[3]);
1215            }
1216            // SDA: a redefinition of a type, with its CCSID.
1217            0x70 => {
1218                ensure!(ll == 12, "an SDA triplet is {ll} bytes, not 12");
1219                let ccsid = u32::from_be_bytes([body[1], body[2], body[3], body[4]]);
1220                let protocol = mdd_protocol_type.take().unwrap_or(id);
1221                overrides.push((id, protocol, ccsid.min(u16::MAX as u32) as u16));
1222            }
1223            // NGDA (the row group) and CPT (its continuation): (type, length) per field.
1224            0x76 | 0x7F => {
1225                ensure!(
1226                    kind == 0x7F || id == 0xD0,
1227                    "the row group triplet has id 0x{id:02X}, not 0xD0"
1228                );
1229                ensure!(
1230                    kind == 0x76 || saw_gda,
1231                    "a continuation triplet precedes its group"
1232                );
1233                saw_gda = true;
1234                ensure!(
1235                    body.len().is_multiple_of(3),
1236                    "a group triplet's entries are not 3 bytes each"
1237                );
1238                for e in body.chunks_exact(3) {
1239                    ensure!(
1240                        fields.len() < MAX_COLUMNS,
1241                        "the answer set has more than {MAX_COLUMNS} columns; refused"
1242                    );
1243                    let length = u16::from_be_bytes([e[1], e[2]]);
1244                    let (drda_type, ccsid) = match overrides.iter().find(|o| o.0 == e[0]) {
1245                        Some(&(_, protocol, ccsid)) => (protocol, Some(ccsid)),
1246                        None => (e[0], None),
1247                    };
1248                    fields.push(Field {
1249                        drda_type,
1250                        length,
1251                        ccsid,
1252                    });
1253                }
1254            }
1255            // RLO: the row is an SQLCA and the row group; the answer set is rows.
1256            0x71 => ensure!(
1257                (id == 0xE0 && ll == 9) || (id == 0xF0 && ll == 6),
1258                "unexpected row layout triplet (id 0x{id:02X}, {ll} bytes)"
1259            ),
1260            other => bail!("QRYDSC triplet type 0x{other:02X} is not one this client reads"),
1261        }
1262        pos += ll;
1263    }
1264    ensure!(saw_gda, "the QRYDSC describes no row group");
1265    Ok(fields)
1266}
1267
1268// ============================================================================
1269// QRYDTA: answer-set rows
1270// ============================================================================
1271
1272/// One row's worth of an answer set.
1273#[derive(Debug, Clone, PartialEq)]
1274pub enum RowItem {
1275    /// A row, with the warning SQLCA the server attached to it, if any.
1276    Row(Vec<Value>, Option<Sqlca>),
1277    /// End of data (SQLCODE +100).
1278    End(Sqlca),
1279    /// The query failed part-way.
1280    Failed(Sqlca),
1281    /// A row position that carries no data (a delete hole).
1282    Empty,
1283}
1284
1285/// Decode the row at the front of `buf`. `Ok(None)`: the row continues in the next query block.
1286pub fn decode_row(buf: &[u8], fields: &[Field], repr: &Repr) -> Result<Option<(RowItem, usize)>> {
1287    let mut r = Reader::new(buf, repr);
1288    match read_row(&mut r, fields) {
1289        Ok(item) => Ok(Some((item, r.pos))),
1290        Err(Short::Incomplete) => Ok(None),
1291        Err(Short::Bad(e)) => Err(e),
1292    }
1293}
1294
1295fn read_row(r: &mut Reader<'_>, fields: &[Field]) -> R<RowItem> {
1296    let ca = read_sqlca(r)?;
1297    let da = r.u8()?;
1298    if let Some(ca) = &ca {
1299        if ca.sqlcode < 0 {
1300            return Ok(RowItem::Failed(ca.clone()));
1301        }
1302        if ca.sqlcode == 100 {
1303            return Ok(RowItem::End(ca.clone()));
1304        }
1305    }
1306    if da & 0x80 != 0 {
1307        return Ok(RowItem::Empty);
1308    }
1309    let mut values = Vec::with_capacity(fields.len());
1310    for (i, f) in fields.iter().enumerate() {
1311        values.push(read_field(r, f, i + 1)?);
1312    }
1313    Ok(RowItem::Row(values, ca))
1314}
1315
1316fn read_field(r: &mut Reader<'_>, f: &Field, column: usize) -> R<Value> {
1317    if f.drda_type & 1 == 1 && r.u8()? & 0x80 != 0 {
1318        return Ok(Value::Null);
1319    }
1320    let len = f.length as usize;
1321    let repr = r.repr;
1322    let sbc = f.ccsid.unwrap_or(repr.sbc);
1323    let mbc = f.ccsid.unwrap_or(repr.mbc);
1324    let dbc = f.ccsid.unwrap_or(repr.dbc);
1325    let fixed_len = |want: usize| -> R<()> {
1326        if len != want {
1327            return Err(Short::Bad(anyhow!(
1328                "column {column}: DRDA type 0x{:02X} described with length {len}, not {want}",
1329                f.drda_type
1330            )));
1331        }
1332        Ok(())
1333    };
1334    let base = f.drda_type & !1;
1335    Ok(match base {
1336        0x02 => {
1337            fixed_len(4)?;
1338            json!(r.i32()?)
1339        }
1340        0x04 => {
1341            fixed_len(2)?;
1342            json!(r.i16()?)
1343        }
1344        0x06 => {
1345            fixed_len(1)?;
1346            json!(r.u8()? as i8)
1347        }
1348        0x16 => {
1349            fixed_len(8)?;
1350            json!(r.i64()?)
1351        }
1352        0x0A | 0x0C if repr.hex_float => {
1353            return Err(Short::Bad(anyhow!(
1354                "column {column} is a hexadecimal (S/390) float, which this client does not \
1355                 decode"
1356            )))
1357        }
1358        0x0A => {
1359            fixed_len(8)?;
1360            float(f64::from_bits(r.i64()? as u64))
1361        }
1362        0x0C => {
1363            fixed_len(4)?;
1364            float(f32::from_bits(r.i32()? as u32) as f64)
1365        }
1366        0x0E => decimal(r, f.length, column)?,
1367        0x20 | 0x22 | 0x24 | 0x30 => text_fixed(r, len, sbc)?,
1368        0x3C => text_fixed(r, len, mbc)?,
1369        0x36 => text_fixed(r, len * 2, dbc)?,
1370        0x32 | 0x34 => text_var(r, len, sbc, 1, column)?,
1371        0x3E | 0x40 => text_var(r, len, mbc, 1, column)?,
1372        0x38 | 0x3A => text_var(r, len, dbc, 2, column)?,
1373        0x46 | 0x48 => {
1374            let n = r.u8()? as usize;
1375            json!(decode_text(
1376                r.take(n)?,
1377                if base == 0x46 { sbc } else { mbc }
1378            )?)
1379        }
1380        0x26 => {
1381            r.take(len)?;
1382            json!({ "binary_length": len })
1383        }
1384        0x28 | 0x2A | 0x1E => {
1385            let n = r.u16()? as usize;
1386            r.take(n)?;
1387            json!({ "binary_length": n })
1388        }
1389        0x44 => {
1390            let n = r.u8()? as usize;
1391            r.take(n)?;
1392            json!({ "binary_length": n })
1393        }
1394        0xBE => {
1395            fixed_len(1)?;
1396            json!(r.u8()? != 0)
1397        }
1398        other => {
1399            return Err(Short::Bad(anyhow!(
1400                "column {column} has DRDA type 0x{other:02X}{}, which this client does not \
1401                 decode (LOBs, locators, XML, DECFLOAT and user-defined types are not read)",
1402                if f.drda_type & 1 == 1 {
1403                    " (nullable)"
1404                } else {
1405                    ""
1406                }
1407            )))
1408        }
1409    })
1410}
1411
1412fn float(v: f64) -> Value {
1413    serde_json::Number::from_f64(v)
1414        .map(Value::Number)
1415        .unwrap_or_else(|| json!(v.to_string()))
1416}
1417
1418fn text_fixed(r: &mut Reader<'_>, len: usize, ccsid: u16) -> R<Value> {
1419    if len > MAX_FIXED_FIELD {
1420        return Err(Short::Bad(anyhow!(
1421            "a fixed character field of {len} bytes; at most {MAX_FIXED_FIELD} are read"
1422        )));
1423    }
1424    Ok(json!(decode_text(r.take(len)?, ccsid)?))
1425}
1426
1427fn text_var(r: &mut Reader<'_>, max: usize, ccsid: u16, unit: usize, column: usize) -> R<Value> {
1428    let n = r.u16()? as usize;
1429    if n > max {
1430        return Err(Short::Bad(anyhow!(
1431            "column {column}: a value of {n} characters in a column described as at most {max}"
1432        )));
1433    }
1434    Ok(json!(decode_text(r.take(n * unit)?, ccsid)?))
1435}
1436
1437/// Packed decimal: `precision/2 + 1` bytes of BCD digits, the last nibble the sign. The value
1438/// is delivered as a string so no digit is lost to a float.
1439fn decimal(r: &mut Reader<'_>, length: u16, column: usize) -> R<Value> {
1440    let precision = (length >> 8) as usize;
1441    let scale = (length & 0xFF) as usize;
1442    if precision == 0 || precision > MAX_DECIMAL_PRECISION || scale > precision {
1443        return Err(Short::Bad(anyhow!(
1444            "column {column}: DECIMAL({precision},{scale}) is not a packed decimal DRDA allows"
1445        )));
1446    }
1447    let bytes = r.take(precision / 2 + 1)?;
1448    let mut digits = String::with_capacity(precision + 1);
1449    for (i, &b) in bytes.iter().enumerate() {
1450        let hi = b >> 4;
1451        let lo = b & 0x0F;
1452        let last = i + 1 == bytes.len();
1453        for (n, d) in [(0, hi), (1, lo)] {
1454            if last && n == 1 {
1455                break;
1456            }
1457            if d > 9 {
1458                return Err(Short::Bad(anyhow!(
1459                    "column {column}: packed decimal digit 0x{d:X}"
1460                )));
1461            }
1462            digits.push((b'0' + d) as char);
1463        }
1464    }
1465    let sign = bytes.last().map(|b| b & 0x0F).unwrap_or(0x0C);
1466    let negative = match sign {
1467        0x0B | 0x0D => true,
1468        0x0A | 0x0C | 0x0E | 0x0F => false,
1469        s => {
1470            return Err(Short::Bad(anyhow!(
1471                "column {column}: packed decimal sign nibble 0x{s:X}"
1472            )))
1473        }
1474    };
1475    // An even precision has one pad digit at the front.
1476    let digits = &digits[digits.len() - precision..];
1477    let (int, frac) = digits.split_at(precision - scale);
1478    let int = int.trim_start_matches('0');
1479    let mut out = String::new();
1480    if negative && digits.bytes().any(|d| d != b'0') {
1481        out.push('-');
1482    }
1483    out.push_str(if int.is_empty() { "0" } else { int });
1484    if scale > 0 {
1485        out.push('.');
1486        out.push_str(frac);
1487    }
1488    Ok(json!(out))
1489}
1490
1491/// Pair decoded values with column names into one JSON object per row. Names a statement
1492/// repeats get `#2`, `#3`, ...; an unnamed column is `COLn`.
1493pub fn row_object(columns: &[Column], values: Vec<Value>) -> Value {
1494    let mut obj = Map::new();
1495    for (i, v) in values.into_iter().enumerate() {
1496        let base = columns
1497            .get(i)
1498            .map(|c| c.name.clone())
1499            .filter(|n| !n.is_empty())
1500            .unwrap_or_else(|| format!("COL{}", i + 1));
1501        let mut key = base.clone();
1502        let mut n = 2;
1503        while obj.contains_key(&key) {
1504            key = format!("{base}#{n}");
1505            n += 1;
1506        }
1507        obj.insert(key, v);
1508    }
1509    Value::Object(obj)
1510}