1pub mod constants;
2pub mod models;
3pub mod protocol;
4
5use byteorder::{ByteOrder, ReadBytesExt, WriteBytesExt};
6
7use chrono::{DateTime, FixedOffset, TimeZone};
8use std::collections::HashMap;
9use std::io::{self, Read, Write};
10use std::net::{TcpStream, UdpSocket};
11use std::time::Duration;
12
13use thiserror::Error;
14
15use crate::constants::*;
16use crate::models::{Attendance, User};
17use crate::protocol::{TCPWrapper, ZKPacket};
18
19#[derive(Error, Debug)]
20pub enum ZKError {
21 #[error("Network error: {0}")]
22 Network(#[from] io::Error),
23 #[error("Connection error: {0}")]
24 Connection(String),
25 #[error("Response error: {0}")]
26 Response(String),
27 #[error("Invalid data: {0}")]
28 InvalidData(String),
29}
30
31pub type ZKResult<T> = Result<T, ZKError>;
32
33pub enum ZKTransport {
34 Tcp(TcpStream),
35 Udp(UdpSocket),
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
39pub enum ZKProtocol {
40 TCP,
41 UDP,
42 Auto,
43}
44
45pub struct ZK {
46 pub addr: String,
47 pub transport: Option<ZKTransport>,
48 pub session_id: u16,
49 pub reply_id: u16,
50 pub timeout: Duration,
51 pub user_map: HashMap<String, String>, pub is_connected: bool,
53 pub user_packet_size: usize,
54 pub users: u32, pub fingers: u32, pub records: u32, pub cards: i32,
58 pub faces: u32, pub fingers_cap: i32,
60 pub users_cap: i32,
61 pub rec_cap: i32,
62 pub faces_cap: i32,
63 pub encoding: String,
64 pub password: u32,
65 pub timezone_offset: i32, }
67
68impl ZK {
69 pub fn new(addr: &str, port: u16) -> Self {
70 ZK {
71 addr: format!("{}:{}", addr, port),
72 transport: None,
73 session_id: 0,
74 reply_id: USHRT_MAX - 1,
75 timeout: Duration::from_secs(60),
76 user_map: HashMap::new(), is_connected: false,
78 user_packet_size: 28,
79 users: 0,
80 fingers: 0,
81 records: 0,
82 cards: 0,
83 faces: 0,
84 fingers_cap: 0,
85 users_cap: 0,
86 rec_cap: 0,
87 faces_cap: 0,
88 encoding: "UTF-8".to_string(),
89 password: 0,
90 timezone_offset: 0,
91 }
92 }
93
94 pub fn set_password(&mut self, password: u32) {
95 self.password = password;
96 }
97
98 fn make_commkey(key: u32, session_id: u16, ticks: u8) -> Vec<u8> {
99 let mut k = 0u32;
100 for i in 0..32 {
101 if (key & (1 << i)) != 0 {
102 k = (k << 1) | 1;
103 } else {
104 k <<= 1;
105 }
106 }
107 k = k.wrapping_add(session_id as u32);
108
109 let b1 = (k & 0xFF) as u8 ^ b'Z';
110 let b2 = ((k >> 8) & 0xFF) as u8 ^ b'K';
111 let b3 = ((k >> 16) & 0xFF) as u8 ^ b'S';
112 let b4 = ((k >> 24) & 0xFF) as u8 ^ b'O';
113
114 let k = (b1 as u16) | ((b2 as u16) << 8);
115 let k2 = (b3 as u16) | ((b4 as u16) << 8);
116
117 let c1 = (k2 & 0xFF) as u8 ^ ticks; let c2 = ((k2 >> 8) & 0xFF) as u8 ^ ticks; let c3 = ticks;
120 let c4 = ((k >> 8) & 0xFF) as u8 ^ ticks; vec![c1, c2, c3, c4]
123 }
124
125 pub fn connect(&mut self, protocol: ZKProtocol) -> ZKResult<()> {
128 match protocol {
129 ZKProtocol::TCP => self.connect_tcp(),
130 ZKProtocol::UDP => self.connect_udp(),
131 ZKProtocol::Auto => {
132 match self.connect_tcp() {
134 Ok(_) => Ok(()),
135 Err(e) => {
136 println!("TCP connect failed: {}. Falling back to UDP...", e);
137 self.connect_udp()
138 }
139 }
140 }
141 }
142 }
143
144 fn connect_tcp(&mut self) -> ZKResult<()> {
145 let stream = TcpStream::connect_timeout(
146 &self.addr.parse().unwrap(),
147 Duration::from_secs(5), )?;
149 stream.set_read_timeout(Some(self.timeout))?;
150 stream.set_write_timeout(Some(self.timeout))?;
151
152 self.transport = Some(ZKTransport::Tcp(stream));
153 self.perform_connect_handshake()
154 }
155
156 fn connect_udp(&mut self) -> ZKResult<()> {
157 let socket = UdpSocket::bind("0.0.0.0:0")?;
158 socket.connect(&self.addr)?;
159 socket.set_read_timeout(Some(self.timeout))?;
160 socket.set_write_timeout(Some(self.timeout))?;
161
162 self.transport = Some(ZKTransport::Udp(socket));
163 self.perform_connect_handshake()
164 }
165
166 fn perform_connect_handshake(&mut self) -> ZKResult<()> {
167 self.session_id = 0;
168 self.reply_id = USHRT_MAX - 1;
169
170 let res = self.send_command(CMD_CONNECT, Vec::new())?;
171
172 if res.command == CMD_ACK_OK || res.command == CMD_ACK_UNAUTH {
174 self.session_id = res.session_id;
175 }
176
177 if res.command == CMD_ACK_UNAUTH {
178 let command_string = ZK::make_commkey(self.password, self.session_id, 50);
179 let auth_res = self.send_command(CMD_AUTH, command_string)?;
180 if auth_res.command == CMD_ACK_UNAUTH {
181 return Err(ZKError::Connection(
182 "Unauthorized: Password required or incorrect".into(),
183 ));
184 }
185 self.session_id = auth_res.session_id;
186 self.is_connected = true;
187 return Ok(());
188 }
189
190 if res.command == CMD_ACK_OK {
191 self.is_connected = true;
192 Ok(())
193 } else {
194 Err(ZKError::Connection(format!(
195 "Invalid response: {}",
196 res.command
197 )))
198 }
199 }
200
201 pub fn send_command(&mut self, command: u16, payload: Vec<u8>) -> ZKResult<ZKPacket> {
202 self.reply_id = self.reply_id.wrapping_add(1);
203 if self.reply_id == USHRT_MAX {
204 self.reply_id -= USHRT_MAX;
205 }
206
207 let packet = ZKPacket::new(command, self.session_id, self.reply_id, payload);
208 let bytes = packet.to_bytes();
209
210 let transport = self
211 .transport
212 .as_mut()
213 .ok_or_else(|| ZKError::Connection("Not connected".into()))?;
214
215 match transport {
216 ZKTransport::Tcp(stream) => {
217 let wrapped = TCPWrapper::wrap(&bytes);
218 stream.write_all(&wrapped)?;
219
220 let mut buf = vec![0u8; 1040];
224 let mut n = stream.read(&mut buf)?;
225 if n == 0 {
226 return Err(ZKError::Network(io::Error::new(
227 io::ErrorKind::UnexpectedEof,
228 "Connection closed",
229 )));
230 }
231 if n < 8 {
233 stream.read_exact(&mut buf[n..8])?;
234 n = 8;
235 }
236 let (length, _) = TCPWrapper::decode_header(&buf[..8])
237 .map_err(|e| ZKError::InvalidData(e.to_string()))?;
238 let total_needed = 8 + length;
239 if n < total_needed {
240 buf.resize(total_needed, 0);
241 stream.read_exact(&mut buf[n..total_needed])?;
242 }
243
244 let res_packet = ZKPacket::from_bytes(&buf[8..8 + length])?;
245 self.reply_id = res_packet.reply_id;
246 Ok(res_packet)
247 }
248 ZKTransport::Udp(socket) => {
249 socket.send(&bytes)?;
250 let mut buf = vec![0u8; 2048];
251 let len = socket.recv(&mut buf)?;
252 let res_packet = ZKPacket::from_bytes(&buf[..len])?;
253 self.reply_id = res_packet.reply_id;
254 Ok(res_packet)
255 }
256 }
257 }
258
259 pub fn read_sizes(&mut self) -> ZKResult<()> {
260 let res = self.send_command(CMD_GET_FREE_SIZES, Vec::new())?;
261 if res.command == CMD_ACK_OK || res.command == CMD_ACK_DATA {
262 let data = res.payload;
263 if data.len() >= 80 {
264 let mut rdr = io::Cursor::new(&data[..80]);
265 let mut fields = [0i32; 20];
266 for field in &mut fields {
267 *field = rdr.read_i32::<byteorder::LittleEndian>()?;
268 }
269 self.users = fields[4] as u32;
270 self.fingers = fields[6] as u32;
271 self.records = fields[8] as u32;
272 self.cards = fields[12];
273 self.fingers_cap = fields[14];
274 self.users_cap = fields[15];
275 self.rec_cap = fields[16];
276 }
277 if data.len() >= 92 {
278 let mut rdr = io::Cursor::new(&data[80..92]);
279 self.faces = rdr.read_i32::<byteorder::LittleEndian>()? as u32;
280 let _ = rdr.read_i32::<byteorder::LittleEndian>()?;
281 self.faces_cap = rdr.read_i32::<byteorder::LittleEndian>()?;
282 }
283 if let Ok(tz_str) = self.get_option_value("TZAdj") {
285 if let Ok(tz_val) = tz_str.parse::<i32>() {
286 self.timezone_offset = tz_val * 60; }
288 }
289 Ok(())
290 } else {
291 Err(ZKError::Response(format!(
292 "Failed to read sizes: {}",
293 res.command
294 )))
295 }
296 }
297
298 pub fn decode_time(t: &[u8]) -> ZKResult<chrono::NaiveDateTime> {
299 if t.len() < 4 {
300 return Err(ZKError::InvalidData("Timestamp too short".into()));
301 }
302 let mut rdr = io::Cursor::new(t);
303 let t = rdr.read_u32::<byteorder::LittleEndian>()?;
304
305 let second = t % 60;
306 let t = t / 60;
307 let minute = t % 60;
308 let t = t / 60;
309 let hour = t % 24;
310 let t = t / 24;
311 let day = t % 31 + 1;
312 let t = t / 31;
313 let month = t % 12 + 1;
314 let t = t / 12;
315 let year = (t + 2000) as i32;
316
317 chrono::NaiveDate::from_ymd_opt(year, month, day)
318 .and_then(|d: chrono::NaiveDate| d.and_hms_opt(hour, minute, second))
319 .ok_or_else(|| ZKError::InvalidData("Invalid date/time".into()))
320 }
321
322 fn receive_chunk(&mut self, res: ZKPacket) -> ZKResult<Vec<u8>> {
323 if res.command == CMD_DATA {
324 Ok(res.payload)
325 } else if res.command == CMD_ACK_OK {
326 std::thread::sleep(std::time::Duration::from_millis(10));
329 Ok(Vec::new())
330 } else if res.command == CMD_PREPARE_DATA {
331 if res.payload.len() < 4 {
332 return Err(ZKError::InvalidData("Invalid prepare data payload".into()));
333 }
334 let size = byteorder::LittleEndian::read_u32(&res.payload[..4]) as usize;
335
336 if size > MAX_RESPONSE_SIZE {
337 return Err(ZKError::InvalidData(format!(
338 "Response size {} exceeds maximum {}",
339 size, MAX_RESPONSE_SIZE
340 )));
341 }
342
343 let mut data = Vec::with_capacity(size);
344 let mut remaining = size;
345
346 while remaining > 0 {
347 let transport = self
348 .transport
349 .as_mut()
350 .ok_or_else(|| ZKError::Connection("Not connected".into()))?;
351 let chunk_res = match transport {
352 ZKTransport::Tcp(stream) => {
353 let mut buf = vec![0u8; 65544]; let mut n = stream.read(&mut buf)?;
355 if n == 0 {
356 return Err(ZKError::Network(io::Error::new(
357 io::ErrorKind::UnexpectedEof,
358 "Connection closed during chunk",
359 )));
360 }
361 if n < 8 {
362 stream.read_exact(&mut buf[n..8])?;
363 n = 8;
364 }
365 let (length, _) = TCPWrapper::decode_header(&buf[..8])
366 .map_err(|e| ZKError::InvalidData(e.to_string()))?;
367 let total_needed = 8 + length;
368 if n < total_needed {
369 buf.resize(total_needed, 0);
370 stream.read_exact(&mut buf[n..total_needed])?;
371 }
372 ZKPacket::from_bytes(&buf[8..8 + length])?
373 }
374 ZKTransport::Udp(socket) => {
375 let mut buf = vec![0u8; 2048];
376 let len = socket.recv(&mut buf)?;
377 ZKPacket::from_bytes(&buf[..len])?
378 }
379 };
380
381 if chunk_res.command == CMD_DATA {
382 data.extend_from_slice(&chunk_res.payload);
383 if remaining >= chunk_res.payload.len() {
384 remaining -= chunk_res.payload.len();
385 } else {
386 remaining = 0;
387 }
388 } else if chunk_res.command == CMD_ACK_OK {
389 break;
390 } else {
391 return Err(ZKError::Response(format!(
392 "Unexpected chunk command: {}",
393 chunk_res.command
394 )));
395 }
396 }
397 Ok(data)
398 } else {
399 Err(ZKError::Response(format!(
400 "Invalid response for chunk: {}",
401 res.command
402 )))
403 }
404 }
405
406 fn read_chunk(&mut self, start: i32, size: i32) -> ZKResult<Vec<u8>> {
407 let mut payload = Vec::new();
408 payload.write_i32::<byteorder::LittleEndian>(start)?;
409 payload.write_i32::<byteorder::LittleEndian>(size)?;
410
411 let res = self.send_command(_CMD_READ_BUFFER, payload)?;
412 self.receive_chunk(res)
413 }
414
415 pub fn read_with_buffer(&mut self, command: u16, fct: u8, ext: u32) -> ZKResult<Vec<u8>> {
416 let mut payload = Vec::new();
417 payload.write_u8(1)?; payload.write_u16::<byteorder::LittleEndian>(command)?;
419 payload.write_u32::<byteorder::LittleEndian>(fct as u32)?;
420 payload.write_u32::<byteorder::LittleEndian>(ext)?;
421
422 let res = self.send_command(_CMD_PREPARE_BUFFER, payload)?;
423 if res.command == CMD_DATA {
424 return Ok(res.payload);
425 }
426
427 let size = if res.payload.len() >= 5 {
428 byteorder::LittleEndian::read_u32(&res.payload[1..5]) as usize
429 } else if res.command == CMD_ACK_OK && res.payload.len() >= 4 {
430 byteorder::LittleEndian::read_u32(&res.payload[0..4]) as usize
432 } else {
433 0
434 };
435
436 if size > MAX_RESPONSE_SIZE {
437 return Err(ZKError::InvalidData(format!(
438 "Buffered response size {} exceeds maximum {}",
439 size, MAX_RESPONSE_SIZE
440 )));
441 }
442
443 let max_chunk = if let Some(ZKTransport::Tcp(_)) = self.transport {
444 TCP_MAX_CHUNK
445 } else {
446 UDP_MAX_CHUNK
447 };
448
449 let mut data = Vec::with_capacity(size);
450 let mut start = 0;
451 let mut remaining = size;
452 let mut empty_responses_count = 0;
453
454 while remaining > 0 {
455 let chunk_size = std::cmp::min(remaining, max_chunk);
456 let chunk = self.read_chunk(start as i32, chunk_size as i32)?;
457
458 if chunk.is_empty() {
459 empty_responses_count += 1;
460 if empty_responses_count > 100 {
461 return Err(ZKError::Response(
462 "Too many empty responses from device".into(),
463 ));
464 }
465 continue;
467 }
468
469 empty_responses_count = 0; data.extend_from_slice(&chunk);
471 start += chunk.len();
472 if remaining >= chunk.len() {
473 remaining -= chunk.len();
474 } else {
475 remaining = 0;
476 }
477 }
478
479 let _ = self.send_command(CMD_FREE_DATA, Vec::new());
481
482 Ok(data)
483 }
484
485 fn decode_gbk(bytes: &[u8]) -> String {
486 let trimmed = bytes
487 .iter()
488 .position(|&x| x == 0)
489 .map_or(bytes, |i| &bytes[..i]);
490 let (cow, _, _) = encoding_rs::GBK.decode(trimmed);
491 cow.into_owned()
492 }
493
494 pub fn get_users(&mut self) -> ZKResult<Vec<User>> {
495 self.read_sizes()?;
496 if self.users == 0 {
497 return Ok(Vec::new());
498 }
499
500 let userdata = self.read_with_buffer(CMD_USERTEMP_RRQ, FCT_USER, 0)?;
501 if userdata.len() <= 4 {
502 return Ok(Vec::new());
503 }
504
505 let total_size = byteorder::LittleEndian::read_u32(&userdata[0..4]) as usize;
506 self.user_packet_size = total_size / self.users as usize;
507 let data = &userdata[4..];
508
509 let mut users = Vec::new();
510 let mut offset = 0;
511
512 if self.user_packet_size == 28 {
513 while offset + 28 <= data.len() {
514 let chunk = &data[offset..offset + 28];
515 let mut rdr = io::Cursor::new(chunk);
516 let uid = rdr.read_u16::<byteorder::LittleEndian>()?;
517 let privilege = rdr.read_u8()?;
518 let mut password_bytes = [0u8; 5];
519 rdr.read_exact(&mut password_bytes)?;
520 let mut name_bytes = [0u8; 8];
521 rdr.read_exact(&mut name_bytes)?;
522 let card = rdr.read_u32::<byteorder::LittleEndian>()?;
523 let _pad = rdr.read_u8()?;
524 let group_id = rdr.read_u8()?;
525 let _timezone = rdr.read_u16::<byteorder::LittleEndian>()?;
526 let user_id = rdr.read_u32::<byteorder::LittleEndian>()?;
527
528 users.push(User {
529 uid,
530 name: ZK::decode_gbk(&name_bytes),
531 privilege,
532 password: String::from_utf8_lossy(&password_bytes)
533 .trim_matches('\0')
534 .to_string(),
535 group_id: group_id.to_string(),
536 user_id: user_id.to_string(),
537 card,
538 });
539 offset += 28;
540 }
541 } else if self.user_packet_size == 72 {
542 while offset + 72 <= data.len() {
543 let chunk = &data[offset..offset + 72];
544 let mut rdr = io::Cursor::new(chunk);
545 let uid = rdr.read_u16::<byteorder::LittleEndian>()?;
546 let privilege = rdr.read_u8()?;
547 let mut password_bytes = [0u8; 8];
548 rdr.read_exact(&mut password_bytes)?;
549 let mut name_bytes = [0u8; 24];
550 rdr.read_exact(&mut name_bytes)?;
551 let card = rdr.read_u32::<byteorder::LittleEndian>()?;
552 let mut group_id_bytes = [0u8; 7]; rdr.read_exact(&mut group_id_bytes)?;
554 let mut user_id_bytes = [0u8; 24];
555 rdr.read_exact(&mut user_id_bytes)?;
556
557 users.push(User {
558 uid,
559 name: ZK::decode_gbk(&name_bytes),
560 privilege,
561 password: String::from_utf8_lossy(&password_bytes)
562 .trim_matches('\0')
563 .to_string(),
564 group_id: String::from_utf8_lossy(&group_id_bytes)
565 .trim_matches('\0')
566 .to_string(),
567 user_id: String::from_utf8_lossy(&user_id_bytes)
568 .trim_matches('\0')
569 .to_string(),
570 card,
571 });
572 offset += 72;
573 }
574 }
575
576 Ok(users)
577 }
578
579 pub fn get_attendance(&mut self) -> ZKResult<Vec<Attendance>> {
580 self.read_sizes()?;
581 if self.records == 0 {
582 return Ok(Vec::new());
583 }
584
585 let users = self.get_users()?;
586 let attendance_data = self.read_with_buffer(CMD_ATTLOG_RRQ, 0, 0)?;
587 if attendance_data.len() < 4 {
588 return Ok(Vec::new());
589 }
590
591 let total_size = byteorder::LittleEndian::read_u32(&attendance_data[0..4]) as usize;
592 let record_size = total_size / self.records as usize;
593 let data = &attendance_data[4..];
594
595 let mut attendances = Vec::new();
596 let mut offset = 0;
597
598 if record_size == 8
599 || (record_size > 0 && total_size.wrapping_rem(8) == 0 && record_size < 16)
600 {
601 while offset + 8 <= data.len() {
602 let chunk = &data[offset..offset + 8];
603 let mut rdr = io::Cursor::new(chunk);
604 let uid = rdr.read_u16::<byteorder::LittleEndian>()?;
605 let status = rdr.read_u8()?;
606 let mut time_bytes = [0u8; 4];
607 rdr.read_exact(&mut time_bytes)?;
608 let punch = rdr.read_u8()?;
609
610 let timestamp = ZK::decode_time(&time_bytes)?;
611 let user_id = users
612 .iter()
613 .find(|u| u.uid == uid)
614 .map(|u| u.user_id.clone())
615 .unwrap_or_else(|| uid.to_string());
616
617 attendances.push(Attendance {
618 uid: uid as u32,
619 user_id,
620 timestamp,
621 status,
622 punch,
623 timezone_offset: self.timezone_offset,
624 });
625 offset += record_size;
626 }
627 } else if record_size == 16
628 || (record_size > 0 && record_size.wrapping_rem(16) == 0 && record_size < 40)
629 {
630 while offset + 16 <= data.len() {
631 let chunk = &data[offset..offset + 16];
632 let mut rdr = io::Cursor::new(chunk);
633 let user_id_num = rdr.read_u32::<byteorder::LittleEndian>()?;
634 let mut time_bytes = [0u8; 4];
635 rdr.read_exact(&mut time_bytes)?;
636 let status = rdr.read_u8()?;
637 let punch = rdr.read_u8()?;
638 let timestamp = ZK::decode_time(&time_bytes)?;
641 let user_id = user_id_num.to_string();
642 let uid = users
643 .iter()
644 .find(|u| u.user_id == user_id)
645 .map(|u| u.uid as u32)
646 .unwrap_or(user_id_num);
647
648 attendances.push(Attendance {
649 uid,
650 user_id,
651 timestamp,
652 status,
653 punch,
654 timezone_offset: self.timezone_offset,
655 });
656 offset += 16;
657 }
658 } else if record_size >= 40 {
659 while offset + 40 <= data.len() {
660 let chunk = &data[offset..offset + 40];
661 let mut chunk_ptr = chunk;
663 if chunk.starts_with(b"\xff255\x00\x00\x00\x00\x00") {
664 chunk_ptr = &chunk[10..];
665 if chunk_ptr.len() < 30 {
666 break;
667 } }
669
670 let mut rdr = io::Cursor::new(chunk_ptr);
671 let uid = rdr.read_u16::<byteorder::LittleEndian>()?;
672 let mut user_id_bytes = [0u8; 24];
673 rdr.read_exact(&mut user_id_bytes)?;
674 let status = rdr.read_u8()?;
675 let mut time_bytes = [0u8; 4];
676 rdr.read_exact(&mut time_bytes)?;
677 let punch = rdr.read_u8()?;
678
679 let timestamp = ZK::decode_time(&time_bytes)?;
680 let user_id = String::from_utf8_lossy(&user_id_bytes)
681 .trim_matches('\0')
682 .to_string();
683
684 attendances.push(Attendance {
685 uid: uid as u32,
686 user_id,
687 timestamp,
688 status,
689 punch,
690 timezone_offset: self.timezone_offset,
691 });
692 offset += record_size;
693 }
694 }
695
696 Ok(attendances)
697 }
698
699 pub fn get_firmware_version(&mut self) -> ZKResult<String> {
700 let res = self.send_command(CMD_GET_VERSION, Vec::new())?;
701 if res.command == CMD_ACK_OK || res.command == CMD_ACK_DATA {
702 Ok(String::from_utf8_lossy(&res.payload)
703 .trim_matches('\0')
704 .to_string())
705 } else {
706 Err(ZKError::Response("Can't read firmware version".into()))
707 }
708 }
709
710 pub fn get_option_value(&mut self, key: &str) -> ZKResult<String> {
711 let mut command_string = key.as_bytes().to_vec();
712 command_string.push(0);
713 let res = self.send_command(CMD_OPTIONS_RRQ, command_string)?;
714 if res.command == CMD_ACK_OK || res.command == CMD_ACK_DATA {
715 let data = String::from_utf8_lossy(&res.payload)
716 .trim_matches('\0')
717 .to_string();
718 if let Some(pos) = data.find('=') {
720 Ok(data[pos + 1..].to_string())
721 } else {
722 Ok(data)
723 }
724 } else {
725 Err(ZKError::Response(format!("Can't read option {}", key)))
726 }
727 }
728
729 pub fn get_serial_number(&mut self) -> ZKResult<String> {
730 self.get_option_value("~SerialNumber")
731 }
732
733 pub fn get_platform(&mut self) -> ZKResult<String> {
734 self.get_option_value("~Platform")
735 }
736
737 pub fn get_mac(&mut self) -> ZKResult<String> {
738 self.get_option_value("MAC")
739 }
740
741 pub fn get_device_name(&mut self) -> ZKResult<String> {
742 self.get_option_value("~DeviceName")
743 }
744
745 pub fn get_face_version(&mut self) -> ZKResult<String> {
746 self.get_option_value("ZKFaceVersion")
747 }
748
749 pub fn get_fp_version(&mut self) -> ZKResult<String> {
750 self.get_option_value("~ZKFPVersion")
751 }
752
753 pub fn get_time(&mut self) -> ZKResult<DateTime<FixedOffset>> {
754 let res = self.send_command(CMD_GET_TIME, Vec::new())?;
755 if res.command == CMD_ACK_OK || res.command == CMD_ACK_DATA {
756 let naive = ZK::decode_time(&res.payload)?;
757 let offset = FixedOffset::east_opt(self.timezone_offset * 60)
758 .unwrap_or_else(|| FixedOffset::east_opt(0).unwrap());
759
760 offset
761 .from_local_datetime(&naive)
762 .single()
763 .ok_or_else(|| ZKError::InvalidData("Ambiguous time from device".into()))
764 } else {
765 Err(ZKError::Response("Can't get time".into()))
766 }
767 }
768
769 pub fn restart(&mut self) -> ZKResult<()> {
770 self.send_command(CMD_RESTART, Vec::new())?;
771 self.is_connected = false;
772 self.transport = None;
773 Ok(())
774 }
775
776 pub fn poweroff(&mut self) -> ZKResult<()> {
777 self.send_command(CMD_POWEROFF, Vec::new())?;
778 self.is_connected = false;
779 self.transport = None;
780 Ok(())
781 }
782
783 pub fn unlock(&mut self, seconds: u32) -> ZKResult<()> {
784 let mut payload = Vec::new();
785 payload.write_u32::<byteorder::LittleEndian>(seconds * 10)?;
788 let res = self.send_command(CMD_UNLOCK, payload)?;
789 if res.command == CMD_ACK_OK {
790 Ok(())
791 } else {
792 Err(ZKError::Response("Can't open door".into()))
793 }
794 }
795
796 pub fn disconnect(&mut self) -> ZKResult<()> {
797 if self.is_connected {
798 let _ = self.send_command(CMD_EXIT, Vec::new());
799 self.is_connected = false;
800 }
801 self.transport = None;
802 Ok(())
803 }
804}
805
806impl Drop for ZK {
807 fn drop(&mut self) {
808 let _ = self.disconnect();
809 }
810}
811
812#[cfg(test)]
813mod tests {
814 use super::*;
815
816 #[test]
817 fn test_make_commkey() {
818 let key = 0;
821 let session_id = 619;
822 let ticks = 50;
823 let result = ZK::make_commkey(key, session_id, ticks);
824 assert_eq!(result, vec![97, 125, 50, 123]);
825 }
826
827 #[test]
828 fn test_zk_new_default_password() {
829 let zk = ZK::new("192.168.1.201", 4370);
830 assert_eq!(zk.password, 0);
831 }
832
833 #[test]
834 fn test_zk_set_password() {
835 let mut zk = ZK::new("192.168.1.201", 4370);
836 zk.set_password(12345);
837 assert_eq!(zk.password, 12345);
838 }
839
840 #[test]
841 fn test_make_commkey_complex() {
842 let key = 12345;
845 let session_id = 9999;
846 let ticks = 100;
847 let result = ZK::make_commkey(key, session_id, ticks);
848
849 assert_eq!(result.len(), 4);
853 }
854}