TFTP (RFC 1350/2347/2348/2349) client and server in pure MoonBit.
moon check # 类型检查
moon test # 运行测试
moon fmt # 格式化
moon info # 更新 .mbti 接口# 在 6969 端口把 ./tftpboot 目录(含子目录)发布出去,允许覆盖已有文件
moon run cmd/main -- serve --root ./tftpboot --port 6969 --allow-overwrite| 参数 | 说明 | 默认 |
|---|---|---|
| --root DIR | 对外服务的根目录,请求路径被限制在其中 | . |
| --port N | 监听的 UDP 端口 | 69 |
| --allow-overwrite | 允许 WRQ 覆盖已存在的文件 | 关闭(仅新建) |
| --max-sessions N | 并发会话上限 | 32 |
# 把服务器上的 boot.img 下载到本地同名文件
moon run cmd/main -- get 192.168.1.10:69 boot.img
# 指定本地落盘路径,并协商 1428 字节块
moon run cmd/main -- get 192.168.1.10:69 boot.img ./downloads/boot.img --blksize 1428
# 文本文件用 netascii 模式
moon run cmd/main -- get 192.168.1.10:69 config.txt --netascii# 第三个位置参数省略时,远端沿用本地文件名
moon run cmd/main -- put 192.168.1.10:69 ./boot.img
# 显式指定远端文件名
moon run cmd/main -- put 192.168.1.10:69 ./boot.img remote/boot.img| 参数 | 说明 | 默认 |
|---|---|---|
| --blksize N | 协商的块大小(8~65464) | 512 |
| --timeout N | 单包超时(秒) | 5 |
| --retries N | 放弃前的重传次数 | 6 |
| --netascii | 使用 netascii 模式(默认 octet) | 关闭 |
# 终端 A
moon run cmd/main -- serve --root /tmp/tftproot --port 6969 --allow-overwrite
# 终端 B
moon run cmd/main -- put 127.0.0.1:6969 ./big.bin # 上传
moon run cmd/main -- get 127.0.0.1:6969 big.bin ./got.bin # 下载回来
cmp ./big.bin ./got.bin && echo "byte-for-byte identical"///|
async fn download() -> Unit {
let transport = @tftp.SocketTransport::new("192.168.1.10:69")
defer transport.close()
match
@tftp.get(
transport,
"192.168.1.10:69",
"boot.img",
@tftp.ClientOptions::default(),
) {
Ok(outcome) =>
match outcome.received() {
Some(bytes) => println("got \{bytes.length()} bytes")
None => ()
}
Err(_) => println("transfer failed")
}
}| 后端 | 状态 | 说明 |
|---|---|---|
| native | ✅ 支持 | CI 全流程(check / test / build)在 native 上跑 |
| js | ⚠️ 未验证 | 依赖宿主提供 UDP;协议层与状态机可编译,socket 后端未验证 |
| wasm / wasm-gc | ⚠️ 未验证 | 同上,视运行时 socket 能力而定 |
pub(open) trait FileSystem {
async fn read(Self, String) -> Result[Bytes, ServerError]
async fn write(Self, String, Bytes) -> Result[Unit, ServerError]
async fn exists(Self, String) -> Bool
}pub(open) trait ServerTransport {
async fn send(Self, Bytes, peer : String) -> Unit
async fn recv(Self, timeout_ms : Int) -> Recv
fn local_tid(Self) -> String
fn close(Self) -> Unit
}pub trait TidAllocator {
fn allocate(Self, peer : String) -> String
}pub(open) trait Transport {
async fn send(Self, Bytes, peer : String) -> Unit
async fn recv(Self, peer : String, timeout_ms : Int) -> Recv
fn local_peer(Self) -> String
}pub struct ClientOptions {
mode : Mode
block_size : Int
timeout_seconds : Int
max_retries : Int
options : Array[TftpOption]
} derive(Eq, Debug)impl TidAllocator for CounterTidpub struct HostFileSystem {
root : String
}impl FileSystem for HostFileSystemasync fn HostFileSystem::write(self : HostFileSystem, path : String, payload : Bytes) -> Result[Unit, ServerError]impl FileSystem for MemoryFileSystemasync fn write(self : MemoryFileSystem, path : String, payload : Bytes) -> Result[Unit, ServerError]async fn MemoryFileSystem::read(self : MemoryFileSystem, path : String) -> Result[Bytes, ServerError]async fn MemoryFileSystem::write(self : MemoryFileSystem, path : String, payload : Bytes) -> Result[Unit, ServerError]impl Transport for MemoryTransportfn MemoryTransport::pair(left_address : String, right_address : String) -> (MemoryTransport, MemoryTransport)pub(all) enum Packet {
Rrq(String, Mode, Array[TftpOption])
Wrq(String, Mode, Array[TftpOption])
Data(UInt16, Bytes)
Ack(UInt16)
Error(ErrorCode, String)
Oack(Array[TftpOption])
} derive(Eq, Debug)pub struct ServerOptions {
root : String
write_policy : WritePolicy
max_sessions : Int
max_retries : Int
} derive(Eq, Debug)impl Transport for SocketSessionpub struct SocketTransport {
socket : UdpServer
request_peer : String
peer : String
address : String
}impl Transport for SocketTransportfn[U, T] TransferSession::new(transport : U, files : T, peer : String, options : ServerOptions) -> TransferSession[U, T]async fn[U : Transport, T : FileSystem] TransferSession::serve(self : TransferSession[U, T], request : Packet) -> SessionOutcomeasync fn[U : Transport, T] TransferSession::step_write(self : TransferSession[U, T], acknowledged : UInt16, collected : Bytes, max_retries : Int) -> (Bool, SessionOutcome)fn block_after(value : UInt16, reference : UInt16) -> Boolfn block_before(value : UInt16, reference : UInt16) -> Boolfn block_follows(value : UInt16, reference : UInt16) -> Boollet default_block_size : Intlet default_port : Intlet default_timeout : Intasync fn[T : Transport] get(transport : T, remote : String, filename : String, request : ClientOptions) -> Result[TransferOutcome, ClientError]let max_block_size : Intlet max_filename_length : Intlet max_timeout : Intlet min_block_size : Intlet min_timeout : Intfn netascii_decode_all(bytes : Bytes) -> Bytesfn netascii_encode_all(bytes : Bytes) -> Bytesfn next_block(block : UInt16) -> UInt16async fn[T : FileSystem] plan_request(options : ServerOptions, files : T, request : Packet) -> Result[RequestAction, ServerError]async fn[T : Transport] put(transport : T, remote : String, filename : String, data : Bytes, request : ClientOptions) -> Result[TransferOutcome, ClientError]fn resolve_options(requested : RequestedOptions, server_value : (RequestedOption) -> Result[String?, NegotiationError]) -> Result[Options, NegotiationError]async fn[T : Transport] serve_read(transport : T, peer : String, payload : Bytes, accepted : AcceptedOptions, options : ServerOptions) -> Result[Int, ServerError]async fn[T : Transport] serve_write(transport : T, peer : String, accepted : AcceptedOptions, options : ServerOptions) -> Result[Bytes, ServerError]fn take_cstring(bytes : Bytes, offset : Int) -> (String, Int)?fn take_u16(bytes : Bytes, offset : Int) -> (UInt16, Int)?fn timeout_to_ms(seconds : Int) -> IntInstall
Download zipTFTP (RFC 1350/2347/2348/2349) client and server in pure MoonBit.