MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能: - ✅ Categories/Series双视图管理(category_view.rs + import_markdown.rs) - ✅ FUSE Multi-Volume支持(tree_type参数) - ✅ SSH/SFTP/SCP/rsync协议完整实现(4042行) - ✅ NFS/SMB Module Phase 1-3完成 - ✅ Archive Module Phase 1-4完成(2916行) - ✅ Download Center API完整实现 - ✅ S3兼容API实现(560行) Git配置修正: - ✅ 删除错误origin(gitea.momentry.ddns.net) - ✅ 删除m5max128(指向机器名) - ✅ 设置origin = m5max128gitea.momentry.ddns.net/admin/markbase - ✅ 设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase 数据清理: - ✅ 删除38个临时SQLite(保留accusys.sqlite、demo.sqlite) - ✅ 删除.bak、test_*.bin、调试脚本等临时文件 - ✅ 删除临时目录(build/、download files/、raid_test/等) - ✅ 更新.gitignore排除临时文件 架构优化: - 52个文件修改,2434行新增,4739行删除 - Workspace成员整合(16个crate) - 数据库状态:accusys.sqlite保留(主demo测试) 远程同步: - ✅ 准备推送到m5max128gitea(远程Gitea) - ✅ 准备推送到m4minigitea(本地Gitea)
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
|
||||
/// iSCSI PDU (Protocol Data Unit)
|
||||
///
|
||||
/// Based on RFC 3720: iSCSI Basic Header Format
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IscsiPdu {
|
||||
/// Opcode (1 byte)
|
||||
pub opcode: u8,
|
||||
|
||||
/// Flags (1 byte)
|
||||
pub flags: u8,
|
||||
|
||||
/// Reserved (2 bytes)
|
||||
pub reserved: [u8; 2],
|
||||
|
||||
/// Total AHS Length (1 byte)
|
||||
pub total_ahs_len: u8,
|
||||
|
||||
/// Data Segment Length (3 bytes)
|
||||
pub data_segment_len: [u8; 3],
|
||||
|
||||
/// Logical Unit Number (8 bytes)
|
||||
pub lun: u64,
|
||||
|
||||
/// Initiator Task Tag (4 bytes)
|
||||
pub itt: u32,
|
||||
|
||||
/// Target Task Tag (4 bytes)
|
||||
pub ttt: u32,
|
||||
|
||||
/// Command Sequence Number (4 bytes)
|
||||
pub cmd_sn: u32,
|
||||
|
||||
/// Expected Status Sequence Number (4 bytes)
|
||||
pub exp_stat_sn: u32,
|
||||
|
||||
/// Expected Command Sequence Number (4 bytes)
|
||||
pub exp_cmd_sn: u32,
|
||||
|
||||
/// Maximum Command Sequence Number (4 bytes)
|
||||
pub max_cmd_sn: u32,
|
||||
|
||||
/// Data payload
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
/// iSCSI Opcode types
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Opcode {
|
||||
/// SCSI Command
|
||||
ScsiCmd = 0x01,
|
||||
|
||||
/// SCSI Response
|
||||
ScsiResp = 0x21,
|
||||
|
||||
/// Login Command
|
||||
LoginCmd = 0x03,
|
||||
|
||||
/// Login Response
|
||||
LoginResp = 0x23,
|
||||
|
||||
/// Logout Command
|
||||
LogoutCmd = 0x06,
|
||||
|
||||
/// Logout Response
|
||||
LogoutResp = 0x26,
|
||||
|
||||
/// NOP-Out
|
||||
NopOut = 0x00,
|
||||
|
||||
/// NOP-In
|
||||
NopIn = 0x20,
|
||||
|
||||
/// Text Command
|
||||
TextCmd = 0x04,
|
||||
|
||||
/// Text Response
|
||||
TextResp = 0x24,
|
||||
}
|
||||
|
||||
impl IscsiPdu {
|
||||
/// Create a new PDU with given opcode
|
||||
pub fn new(opcode: Opcode) -> Self {
|
||||
Self {
|
||||
opcode: opcode as u8,
|
||||
flags: 0,
|
||||
reserved: [0, 0],
|
||||
total_ahs_len: 0,
|
||||
data_segment_len: [0, 0, 0],
|
||||
lun: 0,
|
||||
itt: 0,
|
||||
ttt: 0xFFFFFFFF, // Reserved value
|
||||
cmd_sn: 0,
|
||||
exp_stat_sn: 0,
|
||||
exp_cmd_sn: 0,
|
||||
max_cmd_sn: 0,
|
||||
data: Bytes::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode PDU to bytes
|
||||
pub fn encode(&self) -> Bytes {
|
||||
let total_len = 48 + self.data.len();
|
||||
let mut buf = BytesMut::with_capacity(total_len);
|
||||
|
||||
// Basic Header Segment (BHS) - 48 bytes
|
||||
buf.put_u8(self.opcode);
|
||||
buf.put_u8(self.flags);
|
||||
buf.put_slice(&self.reserved);
|
||||
buf.put_u8(self.total_ahs_len);
|
||||
buf.put_slice(&self.data_segment_len);
|
||||
buf.put_u64_le(self.lun);
|
||||
buf.put_u32_le(self.itt);
|
||||
buf.put_u32_le(self.ttt);
|
||||
buf.put_u32_le(self.cmd_sn);
|
||||
buf.put_u32_le(self.exp_stat_sn);
|
||||
buf.put_u32_le(self.exp_cmd_sn);
|
||||
buf.put_u32_le(self.max_cmd_sn);
|
||||
|
||||
// Reserved fields (8 bytes to complete 48-byte header)
|
||||
buf.put_bytes(0, 8);
|
||||
|
||||
// Data segment
|
||||
buf.put_slice(&self.data);
|
||||
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
/// Decode PDU from bytes
|
||||
pub fn decode(src: &[u8]) -> io::Result<Self> {
|
||||
if src.len() < 48 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"PDU header too short",
|
||||
));
|
||||
}
|
||||
|
||||
let opcode = src[0];
|
||||
let flags = src[1];
|
||||
let reserved = [src[2], src[3]];
|
||||
let total_ahs_len = src[4];
|
||||
let data_segment_len = [src[5], src[6], src[7]];
|
||||
|
||||
// Read LUN (8 bytes)
|
||||
let lun = u64::from_le_bytes([
|
||||
src[8], src[9], src[10], src[11], src[12], src[13], src[14], src[15],
|
||||
]);
|
||||
|
||||
// Read ITT (4 bytes)
|
||||
let itt = u32::from_le_bytes([src[16], src[17], src[18], src[19]]);
|
||||
|
||||
// Read TTT (4 bytes)
|
||||
let ttt = u32::from_le_bytes([src[20], src[21], src[22], src[23]]);
|
||||
|
||||
// Read CmdSN (4 bytes)
|
||||
let cmd_sn = u32::from_le_bytes([src[24], src[25], src[26], src[27]]);
|
||||
|
||||
// Read ExpStatSN (4 bytes)
|
||||
let exp_stat_sn = u32::from_le_bytes([src[28], src[29], src[30], src[31]]);
|
||||
|
||||
// Read ExpCmdSN (4 bytes)
|
||||
let exp_cmd_sn = u32::from_le_bytes([src[32], src[33], src[34], src[35]]);
|
||||
|
||||
// Read MaxCmdSN (4 bytes)
|
||||
let max_cmd_sn = u32::from_le_bytes([src[36], src[37], src[38], src[39]]);
|
||||
|
||||
// Extract data segment
|
||||
let data_len = Self::parse_data_segment_len(&data_segment_len);
|
||||
let data = if data_len > 0 && src.len() >= 48 + data_len {
|
||||
Bytes::copy_from_slice(&src[48..48 + data_len])
|
||||
} else {
|
||||
Bytes::new()
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
opcode,
|
||||
flags,
|
||||
reserved,
|
||||
total_ahs_len,
|
||||
data_segment_len,
|
||||
lun,
|
||||
itt,
|
||||
ttt,
|
||||
cmd_sn,
|
||||
exp_stat_sn,
|
||||
exp_cmd_sn,
|
||||
max_cmd_sn,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse 3-byte data segment length
|
||||
fn parse_data_segment_len(len: &[u8; 3]) -> usize {
|
||||
((len[0] as usize) << 16) | ((len[1] as usize) << 8) | (len[2] as usize)
|
||||
}
|
||||
|
||||
/// Set data payload
|
||||
pub fn set_data(&mut self, data: Bytes) {
|
||||
self.data = data;
|
||||
let len = self.data.len();
|
||||
self.data_segment_len = [(len >> 16) as u8, (len >> 8) as u8, len as u8];
|
||||
}
|
||||
|
||||
/// Create Login PDU
|
||||
pub fn login_request(initiator_name: &str, target_name: &str) -> Self {
|
||||
let mut pdu = Self::new(Opcode::LoginCmd);
|
||||
|
||||
// Login parameters as text
|
||||
let params = format!(
|
||||
"InitiatorName={}\nTargetName={}\n",
|
||||
initiator_name, target_name
|
||||
);
|
||||
pdu.set_data(Bytes::from(params));
|
||||
|
||||
pdu
|
||||
}
|
||||
|
||||
/// Create NOP-Out PDU (keepalive)
|
||||
pub fn nop_out() -> Self {
|
||||
Self::new(Opcode::NopOut)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_pdu_encode_decode() {
|
||||
let mut pdu = IscsiPdu::new(Opcode::ScsiCmd);
|
||||
pdu.lun = 1;
|
||||
pdu.itt = 42;
|
||||
|
||||
let encoded = pdu.encode();
|
||||
assert_eq!(encoded.len(), 48);
|
||||
|
||||
let decoded = IscsiPdu::decode(&encoded).unwrap();
|
||||
assert_eq!(decoded.opcode, Opcode::ScsiCmd as u8);
|
||||
assert_eq!(decoded.lun, 1);
|
||||
assert_eq!(decoded.itt, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_pdu() {
|
||||
let pdu = IscsiPdu::login_request("iqn.test", "iqn.target");
|
||||
assert_eq!(pdu.opcode, Opcode::LoginCmd as u8);
|
||||
assert!(pdu.data.len() > 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user