pub const DSS_MAGIC: u8 = 0xD0;
pub const DSSFMT_RQSDSS: u8 = 0x01;
pub const DSSFMT_RPYDSS: u8 = 0x02;
pub const DSSFMT_OBJDSS: u8 = 0x03;
pub mod cp {
pub const EXCSAT: u16 = 0x1041; pub const ACCSEC: u16 = 0x106D; pub const SECCHK: u16 = 0x106E; pub const ACCRDB: u16 = 0x2001; pub const EXCSQLIMM: u16 = 0x200A; pub const EXCSQLSTT: u16 = 0x200B; pub const EXCSQLSET: u16 = 0x2014; pub const RDBCMM: u16 = 0x200E; pub const RDBRLLBCK: u16 = 0x200F;
pub const EXCSATRD: u16 = 0x1443; pub const ACCSECRD: u16 = 0x14AC; pub const SECCHKRM: u16 = 0x1219; pub const ACCRDBRM: u16 = 0x2201; pub const SQLCARD: u16 = 0x2408; pub const CMDNSPRM: u16 = 0x1250; pub const ENDUOWRM: u16 = 0x220C; pub const RDBATHRM: u16 = 0x22CB;
pub const EXTNAM: u16 = 0x115E; pub const SRVNAM: u16 = 0x116D; pub const SRVRLSLV: u16 = 0x115A; pub const SRVCLSNM: u16 = 0x1147; pub const MGRLVLLS: u16 = 0x1404; pub const SECMEC: u16 = 0x11A2; pub const SECCHKCD: u16 = 0x11A4; pub const SVRCOD: u16 = 0x1149; pub const RDBNAM: u16 = 0x2110; pub const USRID: u16 = 0x11A0; pub const PASSWORD: u16 = 0x11A1; pub const PRDID: u16 = 0x112E; pub const TYPDEFNAM: u16 = 0x002F; pub const TYPDEFOVR: u16 = 0x0035; pub const SQLSTT: u16 = 0x2414; pub const SQLSTTGRP: u16 = 0x2214; pub const UOWDSP: u16 = 0x2115; pub const CCSIDSBC: u16 = 0x119C; pub const CCSIDMBC: u16 = 0x119E; }
pub mod svrcod {
pub const INFO: u16 = 0;
pub const WARNING: u16 = 4;
pub const ERROR: u16 = 8;
pub const SEVERE: u16 = 16;
}
pub mod secchkcd {
pub const SUCCESS: u8 = 0x00; pub const PASSWORD_INVALID: u8 = 0x0F; pub const USERID_MISSING: u8 = 0x12; pub const USERID_UNKNOWN: u8 = 0x10; }
pub fn encode_object(codepoint: u16, data: &[u8]) -> Vec<u8> {
let len = (data.len() + 4) as u16;
let mut out = Vec::with_capacity(data.len() + 4);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&codepoint.to_be_bytes());
out.extend_from_slice(data);
out
}
pub fn encode_scalar(codepoint: u16, data: &[u8]) -> Vec<u8> {
encode_object(codepoint, data)
}
pub fn encode_scalar_str(codepoint: u16, text: &str) -> Vec<u8> {
encode_object(codepoint, &ascii_to_ebcdic(text))
}
pub fn encode_dss(dss_type: u8, chained: bool, correlator: u16, ddm: &[u8]) -> Vec<u8> {
let total = (ddm.len() + 6) as u16;
let format = if chained {
0x40 | 0x10 | (dss_type & 0x0F)
} else {
dss_type & 0x0F
};
let mut out = Vec::with_capacity(ddm.len() + 6);
out.extend_from_slice(&total.to_be_bytes());
out.push(DSS_MAGIC);
out.push(format);
out.extend_from_slice(&correlator.to_be_bytes());
out.extend_from_slice(ddm);
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedDss {
pub format: u8,
pub dss_type: u8,
pub correlator: u16,
pub codepoint: u16,
pub body: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DrdaError {
Truncated,
BadMagic(u8),
BadLength(u16),
}
impl std::fmt::Display for DrdaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DrdaError::Truncated => write!(f, "truncated DRDA frame"),
DrdaError::BadMagic(b) => write!(f, "bad DDM magic 0x{:02X} (expected 0xD0)", b),
DrdaError::BadLength(l) => write!(f, "impossible DRDA length {}", l),
}
}
}
impl std::error::Error for DrdaError {}
pub fn dss_declared_len(buf: &[u8]) -> Option<usize> {
if buf.len() < 2 {
return None;
}
Some(u16::from_be_bytes([buf[0], buf[1]]) as usize)
}
pub fn parse_dss(buf: &[u8]) -> Result<(ParsedDss, usize), DrdaError> {
if buf.len() < 6 {
return Err(DrdaError::Truncated);
}
let total = u16::from_be_bytes([buf[0], buf[1]]) as usize;
if total < 6 {
return Err(DrdaError::BadLength(total as u16));
}
if buf[2] != DSS_MAGIC {
return Err(DrdaError::BadMagic(buf[2]));
}
let format = buf[3];
let correlator = u16::from_be_bytes([buf[4], buf[5]]);
if buf.len() < total {
return Err(DrdaError::Truncated);
}
let ddm = &buf[6..total];
if ddm.len() < 4 {
return Err(DrdaError::Truncated);
}
let obj_len = u16::from_be_bytes([ddm[0], ddm[1]]) as usize;
if obj_len < 4 {
return Err(DrdaError::BadLength(obj_len as u16));
}
let codepoint = u16::from_be_bytes([ddm[2], ddm[3]]);
let end = obj_len.min(ddm.len());
let body = ddm[4..end].to_vec();
Ok((
ParsedDss {
format,
dss_type: format & 0x0F,
correlator,
codepoint,
body,
},
total,
))
}
pub fn parse_params(body: &[u8]) -> Vec<(u16, Vec<u8>)> {
let mut out = Vec::new();
let mut i = 0;
while i + 4 <= body.len() {
let len = u16::from_be_bytes([body[i], body[i + 1]]) as usize;
let cp = u16::from_be_bytes([body[i + 2], body[i + 3]]);
if len < 4 || i + len > body.len() {
break;
}
out.push((cp, body[i + 4..i + len].to_vec()));
i += len;
}
out
}
pub fn find_param(body: &[u8], codepoint: u16) -> Option<Vec<u8>> {
parse_params(body)
.into_iter()
.find(|(cp, _)| *cp == codepoint)
.map(|(_, v)| v)
}
pub fn sqlcard_success() -> Vec<u8> {
encode_object(cp::SQLCARD, &[0xFF])
}
pub fn sqlcard_error(sqlcode: i32, sqlstate: &str) -> Vec<u8> {
sqlcard(sqlcode, sqlstate, 0)
}
pub fn sqlcard(sqlcode: i32, sqlstate: &str, rows: i32) -> Vec<u8> {
let mut data = Vec::with_capacity(64);
data.push(0x00);
data.extend_from_slice(&sqlcode.to_be_bytes());
let mut state = [b' '; 5];
for (i, b) in sqlstate
.bytes()
.filter(u8::is_ascii_alphanumeric)
.take(5)
.enumerate()
{
state[i] = b;
}
data.extend_from_slice(&state);
data.extend_from_slice(b"NETGET ");
data.push(0x00);
for errd in [0, 0, rows, 0, 0, 0] {
data.extend_from_slice(&errd.to_be_bytes());
}
data.extend_from_slice(&[b' '; 11]);
data.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
data.push(0xFF);
encode_object(cp::SQLCARD, &data)
}
pub fn chain_reply(mut reply: Vec<u8>, request_chained: bool) -> Vec<u8> {
let mut starts = Vec::new();
let mut pos = 0;
while pos + 6 <= reply.len() {
let len = u16::from_be_bytes([reply[pos], reply[pos + 1]]) as usize;
if len < 6 || len & 0x8000 != 0 || pos + len > reply.len() {
return reply;
}
starts.push(pos);
pos += len;
}
for (i, &at) in starts.iter().enumerate() {
let next = starts.get(i + 1);
let chained = next.is_some() || request_chained;
let same = next.is_some_and(|&n| reply[n + 4..n + 6] == reply[at + 4..at + 6]);
let mut format = reply[at + 3] & 0x0F;
if chained {
format |= 0x40;
if same {
format |= 0x10;
}
}
reply[at + 3] = format;
}
reply
}
pub fn decode_sqlstt(body: &[u8]) -> String {
let field = |b: &[u8]| -> Option<(Option<Vec<u8>>, usize)> {
match b.first()? {
0xFF => Some((None, 1)),
0x00 if b.len() >= 5 => {
let n = u32::from_be_bytes([b[1], b[2], b[3], b[4]]) as usize;
let end = 5usize.checked_add(n)?;
(end <= b.len()).then(|| (Some(b[5..end].to_vec()), end))
}
_ => None,
}
};
if let Some((mixed, used)) = field(body) {
let single = field(&body[used..]);
if let Some((single, used2)) = single {
if used + used2 == body.len() {
if let Some(text) = mixed.or(single) {
return String::from_utf8_lossy(&text).trim_end().to_string();
}
}
}
}
ebcdic_to_ascii(body)
}
pub fn ascii_byte_to_ebcdic(c: u8) -> u8 {
match c {
b'A'..=b'I' => 0xC1 + (c - b'A'),
b'J'..=b'R' => 0xD1 + (c - b'J'),
b'S'..=b'Z' => 0xE2 + (c - b'S'),
b'a'..=b'i' => 0x81 + (c - b'a'),
b'j'..=b'r' => 0x91 + (c - b'j'),
b's'..=b'z' => 0xA2 + (c - b's'),
b'0'..=b'9' => 0xF0 + (c - b'0'),
b' ' => 0x40,
b'.' => 0x4B,
b'<' => 0x4C,
b'(' => 0x4D,
b'+' => 0x4E,
b'|' => 0x4F,
b'&' => 0x50,
b'!' => 0x5A,
b'$' => 0x5B,
b'*' => 0x5C,
b')' => 0x5D,
b';' => 0x5E,
b'-' => 0x60,
b'/' => 0x61,
b',' => 0x6B,
b'%' => 0x6C,
b'_' => 0x6D,
b'>' => 0x6E,
b'?' => 0x6F,
b'`' => 0x79,
b':' => 0x7A,
b'#' => 0x7B,
b'@' => 0x7C,
b'\'' => 0x7D,
b'=' => 0x7E,
b'"' => 0x7F,
b'~' => 0xA1,
b'[' => 0xBA,
b']' => 0xBB,
b'{' => 0xC0,
b'}' => 0xD0,
b'\\' => 0xE0,
b'^' => 0xB0,
_ => 0x6F,
}
}
pub fn ebcdic_byte_to_ascii(e: u8) -> u8 {
match e {
0xC1..=0xC9 => b'A' + (e - 0xC1),
0xD1..=0xD9 => b'J' + (e - 0xD1),
0xE2..=0xE9 => b'S' + (e - 0xE2),
0x81..=0x89 => b'a' + (e - 0x81),
0x91..=0x99 => b'j' + (e - 0x91),
0xA2..=0xA9 => b's' + (e - 0xA2),
0xF0..=0xF9 => b'0' + (e - 0xF0),
0x40 => b' ',
0x4B => b'.',
0x4C => b'<',
0x4D => b'(',
0x4E => b'+',
0x4F => b'|',
0x50 => b'&',
0x5A => b'!',
0x5B => b'$',
0x5C => b'*',
0x5D => b')',
0x5E => b';',
0x60 => b'-',
0x61 => b'/',
0x6B => b',',
0x6C => b'%',
0x6D => b'_',
0x6E => b'>',
0x6F => b'?',
0x79 => b'`',
0x7A => b':',
0x7B => b'#',
0x7C => b'@',
0x7D => b'\'',
0x7E => b'=',
0x7F => b'"',
0xA1 => b'~',
0xBA => b'[',
0xBB => b']',
0xC0 => b'{',
0xD0 => b'}',
0xE0 => b'\\',
0xB0 => b'^',
_ => b'?',
}
}
pub fn ascii_to_ebcdic(s: &str) -> Vec<u8> {
s.bytes().map(ascii_byte_to_ebcdic).collect()
}
pub fn ascii_to_ebcdic_bytes(b: &[u8]) -> Vec<u8> {
b.iter().map(|&c| ascii_byte_to_ebcdic(c)).collect()
}
pub fn ebcdic_to_ascii(bytes: &[u8]) -> String {
let s: String = bytes
.iter()
.map(|&e| ebcdic_byte_to_ascii(e) as char)
.collect();
s.trim_end().to_string()
}