1use crate::config::ConnectConfig;
40use crate::connection::{AuthState, Handshake, handshake};
41use crate::error::{Error, Result};
42use crate::wire::consts::*;
43use crate::wire::response::read_response;
44use crate::wire::stream::{FbStream, info_payload, op_packet};
45use crate::wire::xdr::ParameterBuffer;
46
47const DEFAULT_INFO_BUF: i32 = 32768;
49
50pub struct ServiceManager {
52 stream: FbStream,
53 handle: i32,
54}
55
56impl ServiceManager {
57 pub fn attach(config: &ConnectConfig) -> Result<ServiceManager> {
60 Self::attach_inner(config)
61 }
62
63 fn attach_inner(config: &ConnectConfig) -> Result<ServiceManager> {
64 let Handshake {
65 mut stream,
66 protocol_version: _,
67 auth,
68 } = handshake(config, op::SERVICE_ATTACH, "service_mgr")?;
69
70 let spb = build_attach_spb(config, &auth);
71 let mut w = op_packet(op::SERVICE_ATTACH);
72 w.put_i32(0); w.put_str("service_mgr");
74 w.put_bytes(&spb);
75 stream.send(&w)?;
76 let resp = crate::connection::attach_response(&mut stream)?;
77
78 Ok(ServiceManager {
79 stream,
80 handle: resp.handle,
81 })
82 }
83
84 pub fn is_encrypted(&self) -> bool {
86 self.stream.is_encrypted()
87 }
88
89 pub fn close(mut self) -> Result<()> {
91 let mut w = op_packet(op::SERVICE_DETACH);
92 w.put_i32(self.handle);
93 self.stream.send(&w)?;
94 let _ = read_response(&mut self.stream)?;
95 Ok(())
96 }
97
98 pub fn info(&mut self, send: &[u8], recv: &[u8], buf_len: i32) -> Result<Vec<u8>> {
106 let mut w = op_packet(op::SERVICE_INFO);
107 w.put_i32(self.handle);
108 w.put_i32(0); w.put_bytes(send); w.put_bytes(recv); w.put_i32(buf_len);
112 self.stream.send(&w)?;
113 let resp = read_response(&mut self.stream)?;
114 Ok(resp.data)
115 }
116
117 pub fn start(&mut self, spb: &[u8]) -> Result<()> {
121 let mut w = op_packet(op::SERVICE_START);
122 w.put_i32(self.handle);
123 w.put_i32(0); w.put_bytes(spb);
125 self.stream.send(&w)?;
126 read_response(&mut self.stream)?;
127 Ok(())
128 }
129
130 pub fn run(&mut self, spb: &[u8]) -> Result<String> {
133 self.start(spb)?;
134 self.collect_output()
135 }
136
137 pub fn collect_output(&mut self) -> Result<String> {
139 let mut out = String::new();
140 loop {
141 let data = self.info(&[], &[svc_info::TO_EOF], DEFAULT_INFO_BUF)?;
142 let chunk = parse_svc_string(&data, svc_info::TO_EOF)?;
143 if chunk.is_empty() {
144 break;
145 }
146 out.push_str(&String::from_utf8_lossy(&chunk));
147 }
148 Ok(out)
149 }
150
151 pub fn server_version(&mut self) -> Result<String> {
155 self.query_string(svc_info::SERVER_VERSION)
156 }
157
158 pub fn implementation(&mut self) -> Result<String> {
160 self.query_string(svc_info::IMPLEMENTATION)
161 }
162
163 pub fn security_database(&mut self) -> Result<String> {
165 self.query_string(svc_info::USER_DBPATH)
166 }
167
168 pub fn home_directory(&mut self) -> Result<String> {
170 self.query_string(svc_info::GET_ENV)
171 }
172
173 pub fn manager_version(&mut self) -> Result<i32> {
176 self.query_int(svc_info::VERSION)
177 }
178
179 pub fn is_running(&mut self) -> Result<bool> {
183 Ok(self.query_int(svc_info::RUNNING)? != 0)
184 }
185
186 pub fn get_fb_log(&mut self) -> Result<String> {
191 let mut spb = ParameterBuffer::raw();
192 spb.tag(svc_action::GET_FB_LOG);
193 self.run(&spb.into_vec())
194 }
195
196 pub fn backup(&mut self, database: &str, backup_file: &str, options: u32) -> Result<String> {
200 let mut spb = ActionSpb::new(svc_action::BACKUP);
201 spb.string(spb::DBNAME, database);
202 spb.string(svc_bkp::FILE, backup_file);
203 if options != 0 {
204 spb.int(spb::OPTIONS, options);
205 }
206 spb.flag(spb::VERBOSE);
207 self.run(&spb.into_vec())
208 }
209
210 pub fn restore(&mut self, backup_file: &str, database: &str, options: u32) -> Result<String> {
215 let mut options = options;
216 if options & (svc_res::REPLACE | svc_res::CREATE) == 0 {
217 options |= svc_res::CREATE;
218 }
219 let mut spb = ActionSpb::new(svc_action::RESTORE);
220 spb.string(svc_bkp::FILE, backup_file);
222 spb.string(spb::DBNAME, database);
223 spb.int(spb::OPTIONS, options);
224 spb.flag(spb::VERBOSE);
225 self.run(&spb.into_vec())
226 }
227
228 pub fn statistics(&mut self, database: &str, options: u32) -> Result<String> {
231 let mut spb = ActionSpb::new(svc_action::DB_STATS);
232 spb.string(spb::DBNAME, database);
233 if options != 0 {
234 spb.int(spb::OPTIONS, options);
235 }
236 self.run(&spb.into_vec())
237 }
238
239 pub fn nbackup(
246 &mut self,
247 database: &str,
248 backup_file: &str,
249 level: u32,
250 options: u32,
251 ) -> Result<String> {
252 let mut spb = ActionSpb::new(svc_action::NBAK);
253 spb.string(spb::DBNAME, database);
254 spb.string(svc_nbk::FILE, backup_file);
255 spb.int(svc_nbk::LEVEL, level);
256 if options != 0 {
257 spb.int(spb::OPTIONS, options);
258 }
259 self.run(&spb.into_vec())
260 }
261
262 pub fn nrestore(
265 &mut self,
266 database: &str,
267 backup_files: &[&str],
268 options: u32,
269 ) -> Result<String> {
270 let mut spb = ActionSpb::new(svc_action::NREST);
271 spb.string(spb::DBNAME, database);
272 for file in backup_files {
273 spb.string(svc_nbk::FILE, file);
274 }
275 if options != 0 {
276 spb.int(spb::OPTIONS, options);
277 }
278 self.run(&spb.into_vec())
279 }
280
281 pub fn validate(
287 &mut self,
288 database: &str,
289 tables: Option<&str>,
290 indices: Option<&str>,
291 ) -> Result<String> {
292 let mut spb = ActionSpb::new(svc_action::VALIDATE);
293 spb.string(spb::DBNAME, database);
294 if let Some(t) = tables {
295 spb.string(svc_val::TAB_INCL, t);
296 }
297 if let Some(i) = indices {
298 spb.string(svc_val::IDX_INCL, i);
299 }
300 self.run(&spb.into_vec())
301 }
302
303 pub fn repair(&mut self, database: &str, options: u32) -> Result<String> {
309 let mut spb = ActionSpb::new(svc_action::REPAIR);
310 spb.string(spb::DBNAME, database);
311 spb.int(spb::OPTIONS, options);
312 self.run(&spb.into_vec())
313 }
314
315 pub fn sweep(&mut self, database: &str) -> Result<String> {
317 self.repair(database, svc_rpr::SWEEP_DB)
318 }
319
320 pub fn set_sweep_interval(&mut self, database: &str, interval: u32) -> Result<()> {
324 self.properties(database, |s| {
325 s.int(svc_prp::SWEEP_INTERVAL, interval);
326 })
327 }
328
329 pub fn set_page_buffers(&mut self, database: &str, buffers: u32) -> Result<()> {
331 self.properties(database, |s| {
332 s.int(svc_prp::PAGE_BUFFERS, buffers);
333 })
334 }
335
336 pub fn set_forced_writes(&mut self, database: &str, sync: bool) -> Result<()> {
338 let mode = if sync {
339 svc_prp::WM_SYNC
340 } else {
341 svc_prp::WM_ASYNC
342 };
343 self.properties(database, |s| {
344 s.byte(svc_prp::WRITE_MODE, mode);
345 })
346 }
347
348 pub fn set_read_only(&mut self, database: &str, read_only: bool) -> Result<()> {
351 let mode = if read_only {
352 svc_prp::AM_READONLY
353 } else {
354 svc_prp::AM_READWRITE
355 };
356 self.properties(database, |s| {
357 s.byte(svc_prp::ACCESS_MODE, mode);
358 })
359 }
360
361 pub fn shutdown(&mut self, database: &str, mode: u8, timeout: u32) -> Result<()> {
364 self.properties(database, |s| {
365 s.byte(svc_prp::SHUTDOWN_MODE, mode);
366 s.int(svc_prp::ATTACHMENTS_SHUTDOWN, timeout);
367 })
368 }
369
370 pub fn bring_online(&mut self, database: &str, mode: u8) -> Result<()> {
372 self.properties(database, |s| {
373 s.byte(svc_prp::ONLINE_MODE, mode);
374 })
375 }
376
377 fn properties<F>(&mut self, database: &str, build: F) -> Result<()>
380 where
381 F: FnOnce(&mut ActionSpb),
382 {
383 let mut spb = ActionSpb::new(svc_action::PROPERTIES);
384 spb.string(spb::DBNAME, database);
385 build(&mut spb);
386 self.run(&spb.into_vec())?;
387 Ok(())
388 }
389
390 pub fn trace_start(&mut self, name: &str, config: &str) -> Result<String> {
399 let mut spb = ActionSpb::new(svc_action::TRACE_START);
400 if !name.is_empty() {
401 spb.string(svc_trc::NAME, name);
402 }
403 spb.string(svc_trc::CFG, config);
404 self.run(&spb.into_vec())
405 }
406
407 pub fn trace_stop(&mut self, session_id: u32) -> Result<String> {
409 self.trace_action(svc_action::TRACE_STOP, session_id)
410 }
411
412 pub fn trace_suspend(&mut self, session_id: u32) -> Result<String> {
414 self.trace_action(svc_action::TRACE_SUSPEND, session_id)
415 }
416
417 pub fn trace_resume(&mut self, session_id: u32) -> Result<String> {
419 self.trace_action(svc_action::TRACE_RESUME, session_id)
420 }
421
422 pub fn trace_list(&mut self) -> Result<String> {
424 let spb = ActionSpb::new(svc_action::TRACE_LIST);
425 self.run(&spb.into_vec())
426 }
427
428 fn trace_action(&mut self, action: u8, session_id: u32) -> Result<String> {
429 let mut spb = ActionSpb::new(action);
430 spb.int(svc_trc::ID, session_id);
431 self.run(&spb.into_vec())
432 }
433
434 pub fn add_user(&mut self, user: &UserParams) -> Result<()> {
438 self.run(&build_user_spb(svc_action::ADD_USER, user))?;
439 Ok(())
440 }
441
442 pub fn modify_user(&mut self, user: &UserParams) -> Result<()> {
445 self.run(&build_user_spb(svc_action::MODIFY_USER, user))?;
446 Ok(())
447 }
448
449 pub fn delete_user(&mut self, username: &str) -> Result<()> {
451 let mut spb = ActionSpb::new(svc_action::DELETE_USER);
452 spb.string(svc_sec::USERNAME, username);
453 self.run(&spb.into_vec())?;
454 Ok(())
455 }
456
457 pub fn display_users(&mut self) -> Result<Vec<UserInfo>> {
460 let spb = ActionSpb::new(svc_action::DISPLAY_USER);
461 self.fetch_users(spb.into_vec())
462 }
463
464 pub fn display_user(&mut self, username: &str) -> Result<Option<UserInfo>> {
466 let mut spb = ActionSpb::new(svc_action::DISPLAY_USER);
467 spb.string(svc_sec::USERNAME, username);
468 Ok(self.fetch_users(spb.into_vec())?.into_iter().next())
469 }
470
471 fn fetch_users(&mut self, spb: Vec<u8>) -> Result<Vec<UserInfo>> {
476 const MAX_INFO_BUF: i32 = 16 * 1024 * 1024;
477 let mut buf_len = DEFAULT_INFO_BUF;
478 loop {
479 self.start(&spb)?;
482 let data = self.info(&[], &[svc_info::GET_USERS], buf_len)?;
483 if data.last() == Some(&INFO_TRUNCATED) && buf_len < MAX_INFO_BUF {
484 buf_len = buf_len.saturating_mul(2).min(MAX_INFO_BUF);
485 continue;
486 }
487 let payload = parse_svc_string(&data, svc_info::GET_USERS)?;
488 return parse_users(&payload);
489 }
490 }
491
492 fn query_string(&mut self, item: u8) -> Result<String> {
496 let data = self.info(&[], &[item], DEFAULT_INFO_BUF)?;
497 let value = parse_svc_string(&data, item)?;
498 Ok(String::from_utf8_lossy(&value).into_owned())
499 }
500
501 fn query_int(&mut self, item: u8) -> Result<i32> {
503 let data = self.info(&[], &[item], DEFAULT_INFO_BUF)?;
504 parse_svc_int(&data, item)
505 }
506}
507
508fn build_attach_spb(config: &ConnectConfig, auth: &AuthState) -> Vec<u8> {
511 let mut spb = ParameterBuffer::raw();
512 spb.tag(SPB_VERSION);
513 spb.tag(SPB_CURRENT_VERSION);
514
515 spb.string(spb::USER_NAME, &config.normalized_user());
516
517 match auth {
518 AuthState::Proof(a) => {
519 spb.string(spb::AUTH_PLUGIN_NAME, &a.plugin);
520 spb.string(spb::AUTH_PLUGIN_LIST, "Srp256,Srp");
521 spb.string(spb::SPECIFIC_AUTH_DATA, &a.proof_hex);
522 }
523 AuthState::Legacy => {
524 spb.string(spb::PASSWORD, &config.password);
525 }
526 AuthState::Done => {}
527 }
528
529 if let Some(role) = &config.role {
530 spb.string(spb::SQL_ROLE_NAME, role);
531 }
532 spb.into_vec()
533}
534
535struct ActionSpb {
540 buf: Vec<u8>,
541}
542
543impl ActionSpb {
544 fn new(action: u8) -> Self {
545 Self { buf: vec![action] }
546 }
547
548 fn string(&mut self, tag: u8, value: &str) -> &mut Self {
550 let bytes = value.as_bytes();
551 self.buf.push(tag);
552 self.buf
553 .extend_from_slice(&(bytes.len() as u16).to_le_bytes());
554 self.buf.extend_from_slice(bytes);
555 self
556 }
557
558 fn int(&mut self, tag: u8, value: u32) -> &mut Self {
560 self.buf.push(tag);
561 self.buf.extend_from_slice(&value.to_le_bytes());
562 self
563 }
564
565 fn byte(&mut self, tag: u8, value: u8) -> &mut Self {
568 self.buf.push(tag);
569 self.buf.push(value);
570 self
571 }
572
573 fn flag(&mut self, tag: u8) -> &mut Self {
575 self.buf.push(tag);
576 self
577 }
578
579 fn into_vec(self) -> Vec<u8> {
580 self.buf
581 }
582}
583
584#[derive(Debug, Clone, Default)]
588pub struct UserParams {
589 username: String,
590 password: Option<String>,
591 first_name: Option<String>,
592 middle_name: Option<String>,
593 last_name: Option<String>,
594 user_id: Option<u32>,
595 group_id: Option<u32>,
596 admin: Option<bool>,
597}
598
599impl UserParams {
600 pub fn new(username: impl Into<String>) -> Self {
602 Self {
603 username: username.into(),
604 ..Default::default()
605 }
606 }
607
608 pub fn password(mut self, v: impl Into<String>) -> Self {
610 self.password = Some(v.into());
611 self
612 }
613
614 pub fn first_name(mut self, v: impl Into<String>) -> Self {
616 self.first_name = Some(v.into());
617 self
618 }
619
620 pub fn middle_name(mut self, v: impl Into<String>) -> Self {
622 self.middle_name = Some(v.into());
623 self
624 }
625
626 pub fn last_name(mut self, v: impl Into<String>) -> Self {
628 self.last_name = Some(v.into());
629 self
630 }
631
632 pub fn user_id(mut self, v: u32) -> Self {
634 self.user_id = Some(v);
635 self
636 }
637
638 pub fn group_id(mut self, v: u32) -> Self {
640 self.group_id = Some(v);
641 self
642 }
643
644 pub fn admin(mut self, v: bool) -> Self {
646 self.admin = Some(v);
647 self
648 }
649}
650
651#[derive(Debug, Clone, Default, PartialEq, Eq)]
654pub struct UserInfo {
655 pub username: String,
657 pub first_name: String,
659 pub middle_name: String,
661 pub last_name: String,
663 pub user_id: u32,
665 pub group_id: u32,
667}
668
669fn build_user_spb(action: u8, p: &UserParams) -> Vec<u8> {
672 let mut spb = ActionSpb::new(action);
673 spb.string(svc_sec::USERNAME, &p.username);
674 if let Some(v) = &p.password {
675 spb.string(svc_sec::PASSWORD, v);
676 }
677 if let Some(v) = &p.first_name {
678 spb.string(svc_sec::FIRSTNAME, v);
679 }
680 if let Some(v) = &p.middle_name {
681 spb.string(svc_sec::MIDDLENAME, v);
682 }
683 if let Some(v) = &p.last_name {
684 spb.string(svc_sec::LASTNAME, v);
685 }
686 if let Some(v) = p.user_id {
687 spb.int(svc_sec::USERID, v);
688 }
689 if let Some(v) = p.group_id {
690 spb.int(svc_sec::GROUPID, v);
691 }
692 if let Some(v) = p.admin {
693 spb.int(svc_sec::ADMIN, v as u32);
694 }
695 spb.into_vec()
696}
697
698fn parse_users(payload: &[u8]) -> Result<Vec<UserInfo>> {
704 let mut users = Vec::new();
705 let mut cur: Option<UserInfo> = None;
706 let mut p = 0usize;
707 while p < payload.len() {
708 let tag = payload[p];
709 p += 1;
710 match tag {
711 svc_sec::USERNAME
712 | svc_sec::GROUPNAME
713 | svc_sec::FIRSTNAME
714 | svc_sec::MIDDLENAME
715 | svc_sec::LASTNAME => {
716 let len = payload
717 .get(p..p + 2)
718 .ok_or_else(|| Error::protocol("get_users: comprimento truncado"))?;
719 let len = u16::from_le_bytes([len[0], len[1]]) as usize;
720 let value = payload
721 .get(p + 2..p + 2 + len)
722 .ok_or_else(|| Error::protocol("get_users: valor truncado"))?;
723 let s = String::from_utf8_lossy(value).into_owned();
724 p += 2 + len;
725 if tag == svc_sec::USERNAME {
726 if let Some(u) = cur.take() {
727 users.push(u);
728 }
729 cur = Some(UserInfo {
730 username: s,
731 ..Default::default()
732 });
733 } else if let Some(u) = cur.as_mut() {
734 match tag {
735 svc_sec::FIRSTNAME => u.first_name = s,
736 svc_sec::MIDDLENAME => u.middle_name = s,
737 svc_sec::LASTNAME => u.last_name = s,
738 _ => {} }
740 }
741 }
742 svc_sec::USERID | svc_sec::GROUPID => {
743 let v = payload
744 .get(p..p + 4)
745 .ok_or_else(|| Error::protocol("get_users: inteiro truncado"))?;
746 let v = u32::from_le_bytes([v[0], v[1], v[2], v[3]]);
747 p += 4;
748 if let Some(u) = cur.as_mut() {
749 if tag == svc_sec::USERID {
750 u.user_id = v;
751 } else {
752 u.group_id = v;
753 }
754 }
755 }
756 _ => break, }
758 }
759 if let Some(u) = cur.take() {
760 users.push(u);
761 }
762 Ok(users)
763}
764
765fn read_svc_item(payload: &[u8]) -> Result<(u8, &[u8])> {
767 if payload.len() < 3 {
768 return Err(Error::protocol("buffer de info de serviço curto demais"));
769 }
770 let tag = payload[0];
771 let len = u16::from_le_bytes([payload[1], payload[2]]) as usize;
772 let value = payload
773 .get(3..3 + len)
774 .ok_or_else(|| Error::protocol("item de info de serviço truncado"))?;
775 Ok((tag, value))
776}
777
778fn parse_svc_string(data: &[u8], expected: u8) -> Result<Vec<u8>> {
781 let payload = info_payload(data)?;
782 if payload.is_empty() {
783 return Ok(Vec::new());
784 }
785 let (tag, value) = read_svc_item(payload)?;
786 if tag != expected {
787 return Err(Error::protocol(format!(
788 "esperava item de serviço {expected}, veio {tag}"
789 )));
790 }
791 Ok(value.to_vec())
792}
793
794fn parse_svc_int(data: &[u8], expected: u8) -> Result<i32> {
798 let payload = info_payload(data)?;
799 if payload.is_empty() {
800 return Err(Error::protocol("buffer de info de serviço vazio"));
801 }
802 if payload[0] != expected {
803 return Err(Error::protocol(format!(
804 "esperava item de serviço {expected}, veio {}",
805 payload[0]
806 )));
807 }
808 let v = payload
809 .get(1..5)
810 .ok_or_else(|| Error::protocol("item inteiro de info de serviço truncado"))?;
811 Ok(i32::from_le_bytes([v[0], v[1], v[2], v[3]]))
812}
813
814#[cfg(test)]
815mod tests {
816 use super::*;
817
818 #[test]
819 fn attach_spb_header_and_user() {
820 let cfg = ConnectConfig::new().user("sysdba");
821 let spb = build_attach_spb(&cfg, &AuthState::Legacy);
822 assert_eq!(spb[0], SPB_VERSION);
824 assert_eq!(spb[1], SPB_CURRENT_VERSION);
825 assert!(spb.windows(6).any(|w| w == b"SYSDBA"));
827 assert!(spb.contains(&spb::PASSWORD));
829 }
830
831 #[test]
832 fn action_spb_byte_and_int_layout() {
833 let mut spb = ActionSpb::new(svc_action::PROPERTIES);
835 spb.string(spb::DBNAME, "db");
836 spb.byte(svc_prp::WRITE_MODE, svc_prp::WM_SYNC);
837 spb.int(svc_prp::SWEEP_INTERVAL, 5000);
838 let v = spb.into_vec();
839 assert_eq!(
840 v,
841 vec![
842 svc_action::PROPERTIES,
843 spb::DBNAME,
844 2,
845 0,
846 b'd',
847 b'b',
848 svc_prp::WRITE_MODE,
849 svc_prp::WM_SYNC,
850 svc_prp::SWEEP_INTERVAL,
851 0x88,
852 0x13,
853 0,
854 0, ]
856 );
857 }
858
859 #[test]
860 fn nbackup_spb_has_level_as_int() {
861 let mut spb = ActionSpb::new(svc_action::NBAK);
862 spb.string(spb::DBNAME, "d");
863 spb.string(svc_nbk::FILE, "f");
864 spb.int(svc_nbk::LEVEL, 0);
865 let v = spb.into_vec();
866 assert_eq!(v[0], svc_action::NBAK);
867 assert!(v.windows(4).any(|w| w == [svc_nbk::FILE, 1, 0, b'f']));
869 assert!(v.ends_with(&[svc_nbk::LEVEL, 0, 0, 0, 0]));
870 }
871
872 #[test]
873 fn parse_int_item_from_strace() {
874 let buf = [svc_info::VERSION, 2, 0, 0, 0, INFO_END];
876 assert_eq!(parse_svc_int(&buf, svc_info::VERSION).unwrap(), 2);
877 assert!(parse_svc_int(&buf, svc_info::RUNNING).is_err());
879 }
880
881 #[test]
882 fn parse_string_item() {
883 let buf = [55u8, 3, 0, b'a', b'b', b'c', INFO_END];
885 let v = parse_svc_string(&buf, svc_info::SERVER_VERSION).unwrap();
886 assert_eq!(v, b"abc");
887 }
888
889 #[test]
890 fn parse_empty_output_is_empty() {
891 let buf = [INFO_END];
892 let v = parse_svc_string(&buf, svc_info::TO_EOF).unwrap();
893 assert!(v.is_empty());
894 }
895
896 #[test]
897 fn action_spb_string_uses_2byte_le_length() {
898 let mut spb = ActionSpb::new(svc_action::DB_STATS);
901 spb.string(spb::DBNAME, "employee");
902 assert_eq!(spb.into_vec(), b"\x0b\x6a\x08\x00employee".to_vec(),);
903 }
904
905 #[test]
906 fn user_spb_add_matches_strace() {
907 let p = UserParams::new("FDBTEST")
911 .password("secret99")
912 .first_name("Test")
913 .last_name("User");
914 let spb = build_user_spb(svc_action::ADD_USER, &p);
915 assert_eq!(
916 spb,
917 b"\x04\x07\x07\x00FDBTEST\x08\x08\x00secret99\x0a\x04\x00Test\x0c\x04\x00User".to_vec(),
918 );
919 }
920
921 #[test]
922 fn user_spb_delete_is_just_username() {
923 let mut spb = ActionSpb::new(svc_action::DELETE_USER);
924 spb.string(svc_sec::USERNAME, "FDBTEST");
925 assert_eq!(spb.into_vec(), b"\x05\x07\x07\x00FDBTEST".to_vec());
926 }
927
928 fn hex(s: &str) -> Vec<u8> {
930 (0..s.len())
931 .step_by(2)
932 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
933 .collect()
934 }
935
936 #[test]
937 fn parse_users_from_strace_payload() {
938 let payload = hex(concat!(
941 "070600",
942 "535953444241", "0a0300",
944 "53716c", "0b0600",
946 "536572766572", "0c0d00",
948 "41646d696e6973747261746f72", "05",
950 "00000000", "06",
952 "00000000", "070800",
954 "4653435343504938", "0a0000",
956 "0b0000",
957 "0c0000", "05",
959 "00000000",
960 "06",
961 "00000000",
962 "070600",
963 "465343534950", "0a0000",
965 "0b0000",
966 "0c0000",
967 "05",
968 "00000000",
969 "06",
970 "00000000",
971 ));
972 let users = parse_users(&payload).unwrap();
973 assert_eq!(users.len(), 3);
974 assert_eq!(users[0].username, "SYSDBA");
975 assert_eq!(users[0].first_name, "Sql");
976 assert_eq!(users[0].middle_name, "Server");
977 assert_eq!(users[0].last_name, "Administrator");
978 assert_eq!(users[1].username, "FSCSCPI8");
979 assert_eq!(users[1].first_name, "");
980 assert_eq!(users[2].username, "FSCSIP");
981 }
982
983 #[test]
984 fn action_spb_backup_matches_strace() {
985 let mut spb = ActionSpb::new(svc_action::BACKUP);
989 spb.string(spb::DBNAME, "db");
990 spb.string(svc_bkp::FILE, "f");
991 spb.int(
992 spb::OPTIONS,
993 svc_bkp::NO_GARBAGE_COLLECT | svc_bkp::IGNORE_CHECKSUMS,
994 );
995 spb.flag(spb::VERBOSE);
996 assert_eq!(
997 spb.into_vec(),
998 vec![
999 svc_action::BACKUP,
1000 spb::DBNAME,
1001 2,
1002 0,
1003 b'd',
1004 b'b',
1005 svc_bkp::FILE,
1006 1,
1007 0,
1008 b'f',
1009 spb::OPTIONS,
1010 0x09,
1011 0,
1012 0,
1013 0, spb::VERBOSE,
1015 ],
1016 );
1017 }
1018}