1use 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
24pub mod cp {
26 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 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 pub const PBSD: u16 = 0xC000;
51 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 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 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
114pub const DSS_RQSDSS: u8 = 0x01;
116pub const DSS_RPYDSS: u8 = 0x02;
117pub const DSS_OBJDSS: u8 = 0x03;
118const DSS_CHAINED: u8 = 0x40;
120const DSS_SAME_CORRELATOR: u8 = 0x10;
121const MAX_SEGMENT: usize = 0x7FFF;
123
124pub const SQLAM_LEVEL: u16 = 7;
126pub const SECMEC_USRIDPWD: u16 = 3;
128pub const SECMEC_USRIDONL: u16 = 4;
130pub const OUR_TYPDEFNAM: &str = "QTDSQLASC";
133pub const CCSID_UTF8: u16 = 1208;
134pub const CCSID_UTF16: u16 = 1200;
135pub 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
143pub const MAX_DSS_BYTES: usize = 8 * 1024 * 1024;
145pub const MAX_REPLY_BYTES: usize = 16 * 1024 * 1024;
147pub const MAX_REPLY_DSS: usize = 4096;
149pub const MAX_OBJECTS_PER_DSS: usize = 256;
151pub const MAX_COLUMNS: usize = 1024;
153pub const MAX_ROWS_LIMIT: usize = 1000;
155pub const DEFAULT_MAX_ROWS: usize = 100;
157pub const QUERY_BLOCK_SIZE: u32 = 32767;
159pub const MAX_RESULT_BYTES: usize = 8 * 1024 * 1024;
161pub const MAX_CONTINUES: usize = 512;
163pub const MAX_SQL_BYTES: usize = 2 * 1024 * 1024;
165pub const MAX_CREDENTIAL_BYTES: usize = 255;
167pub const MAX_RDBNAM_BYTES: usize = 255;
169const NAME_PAD: usize = 18;
171const MAX_FIXED_FIELD: usize = 32767;
173const MAX_DECIMAL_PRECISION: usize = 31;
175
176pub 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
200pub 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); }
213 v
214}
215
216pub 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 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
237fn collection(codepoint: u16, params: &[Vec<u8>]) -> Vec<u8> {
239 object(codepoint, ¶ms.concat())
240}
241
242pub 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
283pub 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#[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
334pub 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
355pub 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, ¶ms))
400}
401
402pub 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
428pub 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
448pub 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
465pub 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
479pub 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
497pub 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#[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
554pub 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 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#[derive(Debug, Clone, PartialEq, Eq)]
613pub struct Object {
614 pub codepoint: u16,
615 pub data: Vec<u8>,
616}
617
618pub 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
638fn 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 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
683pub 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
706pub 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
715pub 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
755pub 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
778pub enum Endian {
779 Big,
780 Little,
781}
782
783#[derive(Clone, Debug, PartialEq, Eq)]
786pub struct Repr {
787 pub typdefnam: String,
788 pub endian: Endian,
789 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 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 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 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
857pub 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#[derive(Debug)]
884pub enum Short {
885 Incomplete,
887 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 fn vc_raw(&mut self) -> R<&'a [u8]> {
949 let n = self.u16()? as usize;
950 self.take(n)
951 }
952 fn vc(&mut self, ccsid: u16) -> R<String> {
954 let raw = self.vc_raw()?;
955 Ok(decode_text(raw, ccsid)?)
956 }
957 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#[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 pub tokens: Vec<String>,
979}
980
981impl Sqlca {
982 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 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
1041pub 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#[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
1102const DERBY_UDT: i16 = 2000;
1105
1106pub 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 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 let c = r.take(2)?;
1136 let ccsid = u16::from_be_bytes([c[0], c[1]]);
1137 let mut name = String::new();
1138 if r.u8()? & 0x80 == 0 {
1140 r.i16()?;
1141 name = r.vcm_or_vcs()?;
1142 r.vcm_or_vcs()?; r.vcm_or_vcs()?; 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1185pub struct Field {
1186 pub drda_type: u8,
1187 pub length: u16,
1188 pub ccsid: Option<u16>,
1190}
1191
1192pub 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(); 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 0x78 => {
1213 ensure!(ll == 7, "an MDD triplet is {ll} bytes, not 7");
1214 mdd_protocol_type = Some(body[3]);
1215 }
1216 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 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 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#[derive(Debug, Clone, PartialEq)]
1274pub enum RowItem {
1275 Row(Vec<Value>, Option<Sqlca>),
1277 End(Sqlca),
1279 Failed(Sqlca),
1281 Empty,
1283}
1284
1285pub 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
1437fn 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 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
1491pub 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}