use crate::config::ConnectConfig;
use crate::connection::{AuthState, Handshake, handshake};
use crate::error::{Error, Result};
use crate::wire::consts::*;
use crate::wire::response::read_response;
use crate::wire::stream::{FbStream, info_payload, op_packet};
use crate::wire::xdr::ParameterBuffer;
const DEFAULT_INFO_BUF: i32 = 32768;
pub struct ServiceManager {
stream: FbStream,
handle: i32,
}
impl ServiceManager {
pub fn attach(config: &ConnectConfig) -> Result<ServiceManager> {
Self::attach_inner(config)
}
fn attach_inner(config: &ConnectConfig) -> Result<ServiceManager> {
let Handshake {
mut stream,
protocol_version: _,
auth,
} = handshake(config, op::SERVICE_ATTACH, "service_mgr")?;
let spb = build_attach_spb(config, &auth);
let mut w = op_packet(op::SERVICE_ATTACH);
w.put_i32(0); w.put_str("service_mgr");
w.put_bytes(&spb);
stream.send(&w)?;
let resp = crate::connection::attach_response(&mut stream)?;
Ok(ServiceManager {
stream,
handle: resp.handle,
})
}
pub fn is_encrypted(&self) -> bool {
self.stream.is_encrypted()
}
pub fn close(mut self) -> Result<()> {
let mut w = op_packet(op::SERVICE_DETACH);
w.put_i32(self.handle);
self.stream.send(&w)?;
let _ = read_response(&mut self.stream)?;
Ok(())
}
pub fn info(&mut self, send: &[u8], recv: &[u8], buf_len: i32) -> Result<Vec<u8>> {
let mut w = op_packet(op::SERVICE_INFO);
w.put_i32(self.handle);
w.put_i32(0); w.put_bytes(send); w.put_bytes(recv); w.put_i32(buf_len);
self.stream.send(&w)?;
let resp = read_response(&mut self.stream)?;
Ok(resp.data)
}
pub fn start(&mut self, spb: &[u8]) -> Result<()> {
let mut w = op_packet(op::SERVICE_START);
w.put_i32(self.handle);
w.put_i32(0); w.put_bytes(spb);
self.stream.send(&w)?;
read_response(&mut self.stream)?;
Ok(())
}
pub fn run(&mut self, spb: &[u8]) -> Result<String> {
self.start(spb)?;
self.collect_output()
}
pub fn collect_output(&mut self) -> Result<String> {
let mut out = String::new();
loop {
let data = self.info(&[], &[svc_info::TO_EOF], DEFAULT_INFO_BUF)?;
let chunk = parse_svc_string(&data, svc_info::TO_EOF)?;
if chunk.is_empty() {
break;
}
out.push_str(&String::from_utf8_lossy(&chunk));
}
Ok(out)
}
pub fn server_version(&mut self) -> Result<String> {
self.query_string(svc_info::SERVER_VERSION)
}
pub fn implementation(&mut self) -> Result<String> {
self.query_string(svc_info::IMPLEMENTATION)
}
pub fn security_database(&mut self) -> Result<String> {
self.query_string(svc_info::USER_DBPATH)
}
pub fn home_directory(&mut self) -> Result<String> {
self.query_string(svc_info::GET_ENV)
}
pub fn manager_version(&mut self) -> Result<i32> {
self.query_int(svc_info::VERSION)
}
pub fn is_running(&mut self) -> Result<bool> {
Ok(self.query_int(svc_info::RUNNING)? != 0)
}
pub fn get_fb_log(&mut self) -> Result<String> {
let mut spb = ParameterBuffer::raw();
spb.tag(svc_action::GET_FB_LOG);
self.run(&spb.into_vec())
}
pub fn backup(&mut self, database: &str, backup_file: &str, options: u32) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::BACKUP);
spb.string(spb::DBNAME, database);
spb.string(svc_bkp::FILE, backup_file);
if options != 0 {
spb.int(spb::OPTIONS, options);
}
spb.flag(spb::VERBOSE);
self.run(&spb.into_vec())
}
pub fn restore(&mut self, backup_file: &str, database: &str, options: u32) -> Result<String> {
let mut options = options;
if options & (svc_res::REPLACE | svc_res::CREATE) == 0 {
options |= svc_res::CREATE;
}
let mut spb = ActionSpb::new(svc_action::RESTORE);
spb.string(svc_bkp::FILE, backup_file);
spb.string(spb::DBNAME, database);
spb.int(spb::OPTIONS, options);
spb.flag(spb::VERBOSE);
self.run(&spb.into_vec())
}
pub fn statistics(&mut self, database: &str, options: u32) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::DB_STATS);
spb.string(spb::DBNAME, database);
if options != 0 {
spb.int(spb::OPTIONS, options);
}
self.run(&spb.into_vec())
}
pub fn nbackup(
&mut self,
database: &str,
backup_file: &str,
level: u32,
options: u32,
) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::NBAK);
spb.string(spb::DBNAME, database);
spb.string(svc_nbk::FILE, backup_file);
spb.int(svc_nbk::LEVEL, level);
if options != 0 {
spb.int(spb::OPTIONS, options);
}
self.run(&spb.into_vec())
}
pub fn nrestore(
&mut self,
database: &str,
backup_files: &[&str],
options: u32,
) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::NREST);
spb.string(spb::DBNAME, database);
for file in backup_files {
spb.string(svc_nbk::FILE, file);
}
if options != 0 {
spb.int(spb::OPTIONS, options);
}
self.run(&spb.into_vec())
}
pub fn validate(
&mut self,
database: &str,
tables: Option<&str>,
indices: Option<&str>,
) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::VALIDATE);
spb.string(spb::DBNAME, database);
if let Some(t) = tables {
spb.string(svc_val::TAB_INCL, t);
}
if let Some(i) = indices {
spb.string(svc_val::IDX_INCL, i);
}
self.run(&spb.into_vec())
}
pub fn repair(&mut self, database: &str, options: u32) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::REPAIR);
spb.string(spb::DBNAME, database);
spb.int(spb::OPTIONS, options);
self.run(&spb.into_vec())
}
pub fn sweep(&mut self, database: &str) -> Result<String> {
self.repair(database, svc_rpr::SWEEP_DB)
}
pub fn set_sweep_interval(&mut self, database: &str, interval: u32) -> Result<()> {
self.properties(database, |s| {
s.int(svc_prp::SWEEP_INTERVAL, interval);
})
}
pub fn set_page_buffers(&mut self, database: &str, buffers: u32) -> Result<()> {
self.properties(database, |s| {
s.int(svc_prp::PAGE_BUFFERS, buffers);
})
}
pub fn set_forced_writes(&mut self, database: &str, sync: bool) -> Result<()> {
let mode = if sync {
svc_prp::WM_SYNC
} else {
svc_prp::WM_ASYNC
};
self.properties(database, |s| {
s.byte(svc_prp::WRITE_MODE, mode);
})
}
pub fn set_read_only(&mut self, database: &str, read_only: bool) -> Result<()> {
let mode = if read_only {
svc_prp::AM_READONLY
} else {
svc_prp::AM_READWRITE
};
self.properties(database, |s| {
s.byte(svc_prp::ACCESS_MODE, mode);
})
}
pub fn shutdown(&mut self, database: &str, mode: u8, timeout: u32) -> Result<()> {
self.properties(database, |s| {
s.byte(svc_prp::SHUTDOWN_MODE, mode);
s.int(svc_prp::ATTACHMENTS_SHUTDOWN, timeout);
})
}
pub fn bring_online(&mut self, database: &str, mode: u8) -> Result<()> {
self.properties(database, |s| {
s.byte(svc_prp::ONLINE_MODE, mode);
})
}
fn properties<F>(&mut self, database: &str, build: F) -> Result<()>
where
F: FnOnce(&mut ActionSpb),
{
let mut spb = ActionSpb::new(svc_action::PROPERTIES);
spb.string(spb::DBNAME, database);
build(&mut spb);
self.run(&spb.into_vec())?;
Ok(())
}
pub fn trace_start(&mut self, name: &str, config: &str) -> Result<String> {
let mut spb = ActionSpb::new(svc_action::TRACE_START);
if !name.is_empty() {
spb.string(svc_trc::NAME, name);
}
spb.string(svc_trc::CFG, config);
self.run(&spb.into_vec())
}
pub fn trace_stop(&mut self, session_id: u32) -> Result<String> {
self.trace_action(svc_action::TRACE_STOP, session_id)
}
pub fn trace_suspend(&mut self, session_id: u32) -> Result<String> {
self.trace_action(svc_action::TRACE_SUSPEND, session_id)
}
pub fn trace_resume(&mut self, session_id: u32) -> Result<String> {
self.trace_action(svc_action::TRACE_RESUME, session_id)
}
pub fn trace_list(&mut self) -> Result<String> {
let spb = ActionSpb::new(svc_action::TRACE_LIST);
self.run(&spb.into_vec())
}
fn trace_action(&mut self, action: u8, session_id: u32) -> Result<String> {
let mut spb = ActionSpb::new(action);
spb.int(svc_trc::ID, session_id);
self.run(&spb.into_vec())
}
pub fn add_user(&mut self, user: &UserParams) -> Result<()> {
self.run(&build_user_spb(svc_action::ADD_USER, user))?;
Ok(())
}
pub fn modify_user(&mut self, user: &UserParams) -> Result<()> {
self.run(&build_user_spb(svc_action::MODIFY_USER, user))?;
Ok(())
}
pub fn delete_user(&mut self, username: &str) -> Result<()> {
let mut spb = ActionSpb::new(svc_action::DELETE_USER);
spb.string(svc_sec::USERNAME, username);
self.run(&spb.into_vec())?;
Ok(())
}
pub fn display_users(&mut self) -> Result<Vec<UserInfo>> {
let spb = ActionSpb::new(svc_action::DISPLAY_USER);
self.fetch_users(spb.into_vec())
}
pub fn display_user(&mut self, username: &str) -> Result<Option<UserInfo>> {
let mut spb = ActionSpb::new(svc_action::DISPLAY_USER);
spb.string(svc_sec::USERNAME, username);
Ok(self.fetch_users(spb.into_vec())?.into_iter().next())
}
fn fetch_users(&mut self, spb: Vec<u8>) -> Result<Vec<UserInfo>> {
const MAX_INFO_BUF: i32 = 16 * 1024 * 1024;
let mut buf_len = DEFAULT_INFO_BUF;
loop {
self.start(&spb)?;
let data = self.info(&[], &[svc_info::GET_USERS], buf_len)?;
if data.last() == Some(&INFO_TRUNCATED) && buf_len < MAX_INFO_BUF {
buf_len = buf_len.saturating_mul(2).min(MAX_INFO_BUF);
continue;
}
let payload = parse_svc_string(&data, svc_info::GET_USERS)?;
return parse_users(&payload);
}
}
fn query_string(&mut self, item: u8) -> Result<String> {
let data = self.info(&[], &[item], DEFAULT_INFO_BUF)?;
let value = parse_svc_string(&data, item)?;
Ok(String::from_utf8_lossy(&value).into_owned())
}
fn query_int(&mut self, item: u8) -> Result<i32> {
let data = self.info(&[], &[item], DEFAULT_INFO_BUF)?;
parse_svc_int(&data, item)
}
}
fn build_attach_spb(config: &ConnectConfig, auth: &AuthState) -> Vec<u8> {
let mut spb = ParameterBuffer::raw();
spb.tag(SPB_VERSION);
spb.tag(SPB_CURRENT_VERSION);
spb.string(spb::USER_NAME, &config.normalized_user());
match auth {
AuthState::Proof(a) => {
spb.string(spb::AUTH_PLUGIN_NAME, &a.plugin);
spb.string(spb::AUTH_PLUGIN_LIST, "Srp256,Srp");
spb.string(spb::SPECIFIC_AUTH_DATA, &a.proof_hex);
}
AuthState::Legacy => {
spb.string(spb::PASSWORD, &config.password);
}
AuthState::Done => {}
}
if let Some(role) = &config.role {
spb.string(spb::SQL_ROLE_NAME, role);
}
spb.into_vec()
}
struct ActionSpb {
buf: Vec<u8>,
}
impl ActionSpb {
fn new(action: u8) -> Self {
Self { buf: vec![action] }
}
fn string(&mut self, tag: u8, value: &str) -> &mut Self {
let bytes = value.as_bytes();
self.buf.push(tag);
self.buf
.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
self.buf.extend_from_slice(bytes);
self
}
fn int(&mut self, tag: u8, value: u32) -> &mut Self {
self.buf.push(tag);
self.buf.extend_from_slice(&value.to_le_bytes());
self
}
fn byte(&mut self, tag: u8, value: u8) -> &mut Self {
self.buf.push(tag);
self.buf.push(value);
self
}
fn flag(&mut self, tag: u8) -> &mut Self {
self.buf.push(tag);
self
}
fn into_vec(self) -> Vec<u8> {
self.buf
}
}
#[derive(Debug, Clone, Default)]
pub struct UserParams {
username: String,
password: Option<String>,
first_name: Option<String>,
middle_name: Option<String>,
last_name: Option<String>,
user_id: Option<u32>,
group_id: Option<u32>,
admin: Option<bool>,
}
impl UserParams {
pub fn new(username: impl Into<String>) -> Self {
Self {
username: username.into(),
..Default::default()
}
}
pub fn password(mut self, v: impl Into<String>) -> Self {
self.password = Some(v.into());
self
}
pub fn first_name(mut self, v: impl Into<String>) -> Self {
self.first_name = Some(v.into());
self
}
pub fn middle_name(mut self, v: impl Into<String>) -> Self {
self.middle_name = Some(v.into());
self
}
pub fn last_name(mut self, v: impl Into<String>) -> Self {
self.last_name = Some(v.into());
self
}
pub fn user_id(mut self, v: u32) -> Self {
self.user_id = Some(v);
self
}
pub fn group_id(mut self, v: u32) -> Self {
self.group_id = Some(v);
self
}
pub fn admin(mut self, v: bool) -> Self {
self.admin = Some(v);
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct UserInfo {
pub username: String,
pub first_name: String,
pub middle_name: String,
pub last_name: String,
pub user_id: u32,
pub group_id: u32,
}
fn build_user_spb(action: u8, p: &UserParams) -> Vec<u8> {
let mut spb = ActionSpb::new(action);
spb.string(svc_sec::USERNAME, &p.username);
if let Some(v) = &p.password {
spb.string(svc_sec::PASSWORD, v);
}
if let Some(v) = &p.first_name {
spb.string(svc_sec::FIRSTNAME, v);
}
if let Some(v) = &p.middle_name {
spb.string(svc_sec::MIDDLENAME, v);
}
if let Some(v) = &p.last_name {
spb.string(svc_sec::LASTNAME, v);
}
if let Some(v) = p.user_id {
spb.int(svc_sec::USERID, v);
}
if let Some(v) = p.group_id {
spb.int(svc_sec::GROUPID, v);
}
if let Some(v) = p.admin {
spb.int(svc_sec::ADMIN, v as u32);
}
spb.into_vec()
}
fn parse_users(payload: &[u8]) -> Result<Vec<UserInfo>> {
let mut users = Vec::new();
let mut cur: Option<UserInfo> = None;
let mut p = 0usize;
while p < payload.len() {
let tag = payload[p];
p += 1;
match tag {
svc_sec::USERNAME
| svc_sec::GROUPNAME
| svc_sec::FIRSTNAME
| svc_sec::MIDDLENAME
| svc_sec::LASTNAME => {
let len = payload
.get(p..p + 2)
.ok_or_else(|| Error::protocol("get_users: comprimento truncado"))?;
let len = u16::from_le_bytes([len[0], len[1]]) as usize;
let value = payload
.get(p + 2..p + 2 + len)
.ok_or_else(|| Error::protocol("get_users: valor truncado"))?;
let s = String::from_utf8_lossy(value).into_owned();
p += 2 + len;
if tag == svc_sec::USERNAME {
if let Some(u) = cur.take() {
users.push(u);
}
cur = Some(UserInfo {
username: s,
..Default::default()
});
} else if let Some(u) = cur.as_mut() {
match tag {
svc_sec::FIRSTNAME => u.first_name = s,
svc_sec::MIDDLENAME => u.middle_name = s,
svc_sec::LASTNAME => u.last_name = s,
_ => {} }
}
}
svc_sec::USERID | svc_sec::GROUPID => {
let v = payload
.get(p..p + 4)
.ok_or_else(|| Error::protocol("get_users: inteiro truncado"))?;
let v = u32::from_le_bytes([v[0], v[1], v[2], v[3]]);
p += 4;
if let Some(u) = cur.as_mut() {
if tag == svc_sec::USERID {
u.user_id = v;
} else {
u.group_id = v;
}
}
}
_ => break, }
}
if let Some(u) = cur.take() {
users.push(u);
}
Ok(users)
}
fn read_svc_item(payload: &[u8]) -> Result<(u8, &[u8])> {
if payload.len() < 3 {
return Err(Error::protocol("buffer de info de serviço curto demais"));
}
let tag = payload[0];
let len = u16::from_le_bytes([payload[1], payload[2]]) as usize;
let value = payload
.get(3..3 + len)
.ok_or_else(|| Error::protocol("item de info de serviço truncado"))?;
Ok((tag, value))
}
fn parse_svc_string(data: &[u8], expected: u8) -> Result<Vec<u8>> {
let payload = info_payload(data)?;
if payload.is_empty() {
return Ok(Vec::new());
}
let (tag, value) = read_svc_item(payload)?;
if tag != expected {
return Err(Error::protocol(format!(
"esperava item de serviço {expected}, veio {tag}"
)));
}
Ok(value.to_vec())
}
fn parse_svc_int(data: &[u8], expected: u8) -> Result<i32> {
let payload = info_payload(data)?;
if payload.is_empty() {
return Err(Error::protocol("buffer de info de serviço vazio"));
}
if payload[0] != expected {
return Err(Error::protocol(format!(
"esperava item de serviço {expected}, veio {}",
payload[0]
)));
}
let v = payload
.get(1..5)
.ok_or_else(|| Error::protocol("item inteiro de info de serviço truncado"))?;
Ok(i32::from_le_bytes([v[0], v[1], v[2], v[3]]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attach_spb_header_and_user() {
let cfg = ConnectConfig::new().user("sysdba");
let spb = build_attach_spb(&cfg, &AuthState::Legacy);
assert_eq!(spb[0], SPB_VERSION);
assert_eq!(spb[1], SPB_CURRENT_VERSION);
assert!(spb.windows(6).any(|w| w == b"SYSDBA"));
assert!(spb.contains(&spb::PASSWORD));
}
#[test]
fn action_spb_byte_and_int_layout() {
let mut spb = ActionSpb::new(svc_action::PROPERTIES);
spb.string(spb::DBNAME, "db");
spb.byte(svc_prp::WRITE_MODE, svc_prp::WM_SYNC);
spb.int(svc_prp::SWEEP_INTERVAL, 5000);
let v = spb.into_vec();
assert_eq!(
v,
vec![
svc_action::PROPERTIES,
spb::DBNAME,
2,
0,
b'd',
b'b',
svc_prp::WRITE_MODE,
svc_prp::WM_SYNC,
svc_prp::SWEEP_INTERVAL,
0x88,
0x13,
0,
0, ]
);
}
#[test]
fn nbackup_spb_has_level_as_int() {
let mut spb = ActionSpb::new(svc_action::NBAK);
spb.string(spb::DBNAME, "d");
spb.string(svc_nbk::FILE, "f");
spb.int(svc_nbk::LEVEL, 0);
let v = spb.into_vec();
assert_eq!(v[0], svc_action::NBAK);
assert!(v.windows(4).any(|w| w == [svc_nbk::FILE, 1, 0, b'f']));
assert!(v.ends_with(&[svc_nbk::LEVEL, 0, 0, 0, 0]));
}
#[test]
fn parse_int_item_from_strace() {
let buf = [svc_info::VERSION, 2, 0, 0, 0, INFO_END];
assert_eq!(parse_svc_int(&buf, svc_info::VERSION).unwrap(), 2);
assert!(parse_svc_int(&buf, svc_info::RUNNING).is_err());
}
#[test]
fn parse_string_item() {
let buf = [55u8, 3, 0, b'a', b'b', b'c', INFO_END];
let v = parse_svc_string(&buf, svc_info::SERVER_VERSION).unwrap();
assert_eq!(v, b"abc");
}
#[test]
fn parse_empty_output_is_empty() {
let buf = [INFO_END];
let v = parse_svc_string(&buf, svc_info::TO_EOF).unwrap();
assert!(v.is_empty());
}
#[test]
fn action_spb_string_uses_2byte_le_length() {
let mut spb = ActionSpb::new(svc_action::DB_STATS);
spb.string(spb::DBNAME, "employee");
assert_eq!(spb.into_vec(), b"\x0b\x6a\x08\x00employee".to_vec(),);
}
#[test]
fn user_spb_add_matches_strace() {
let p = UserParams::new("FDBTEST")
.password("secret99")
.first_name("Test")
.last_name("User");
let spb = build_user_spb(svc_action::ADD_USER, &p);
assert_eq!(
spb,
b"\x04\x07\x07\x00FDBTEST\x08\x08\x00secret99\x0a\x04\x00Test\x0c\x04\x00User".to_vec(),
);
}
#[test]
fn user_spb_delete_is_just_username() {
let mut spb = ActionSpb::new(svc_action::DELETE_USER);
spb.string(svc_sec::USERNAME, "FDBTEST");
assert_eq!(spb.into_vec(), b"\x05\x07\x07\x00FDBTEST".to_vec());
}
fn hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn parse_users_from_strace_payload() {
let payload = hex(concat!(
"070600",
"535953444241", "0a0300",
"53716c", "0b0600",
"536572766572", "0c0d00",
"41646d696e6973747261746f72", "05",
"00000000", "06",
"00000000", "070800",
"4653435343504938", "0a0000",
"0b0000",
"0c0000", "05",
"00000000",
"06",
"00000000",
"070600",
"465343534950", "0a0000",
"0b0000",
"0c0000",
"05",
"00000000",
"06",
"00000000",
));
let users = parse_users(&payload).unwrap();
assert_eq!(users.len(), 3);
assert_eq!(users[0].username, "SYSDBA");
assert_eq!(users[0].first_name, "Sql");
assert_eq!(users[0].middle_name, "Server");
assert_eq!(users[0].last_name, "Administrator");
assert_eq!(users[1].username, "FSCSCPI8");
assert_eq!(users[1].first_name, "");
assert_eq!(users[2].username, "FSCSIP");
}
#[test]
fn action_spb_backup_matches_strace() {
let mut spb = ActionSpb::new(svc_action::BACKUP);
spb.string(spb::DBNAME, "db");
spb.string(svc_bkp::FILE, "f");
spb.int(
spb::OPTIONS,
svc_bkp::NO_GARBAGE_COLLECT | svc_bkp::IGNORE_CHECKSUMS,
);
spb.flag(spb::VERBOSE);
assert_eq!(
spb.into_vec(),
vec![
svc_action::BACKUP,
spb::DBNAME,
2,
0,
b'd',
b'b',
svc_bkp::FILE,
1,
0,
b'f',
spb::OPTIONS,
0x09,
0,
0,
0, spb::VERBOSE,
],
);
}
}