MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

核心功能:
-  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:
Warren
2026-06-12 12:59:54 +08:00
parent 4cb7e80568
commit 1300a4e223
4559 changed files with 195840 additions and 4244 deletions
+251
View File
@@ -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);
}
}