diff --git a/doc/lisp.md b/doc/lisp.md index 2eed6375e..2739b55b8 100644 --- a/doc/lisp.md +++ b/doc/lisp.md @@ -65,6 +65,7 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby! - `let` - `string.join` (aliased to `str.join`), `lines`, `words`, `chars` - `regex.match?` +- `socket.connect`, `socket.listen`, `socket.accept` ### File Library - `read`, `write`, `append` @@ -199,3 +200,4 @@ language and reading from the filesystem. ### Unreleased - Add file, number, string, and regex namespaces +- Add socket functions diff --git a/doc/network.md b/doc/network.md index a523a55b3..5ff3162f0 100644 --- a/doc/network.md +++ b/doc/network.md @@ -61,14 +61,14 @@ The `dhcp` command configures the network automatically: The `host` command performs DNS lookups: > host example.com - example.com has address 93.184.216.34 + 93.184.216.34 ## TCP The `tcp` command connects to TCP sockets: - > tcp time.nist.gov 13 --verbose + > tcp time.nist.gov:13 --verbose DEBUG: Connecting to 129.6.15.30:13 58884 20-02-05 19:19:42 00 0 0 49.2 UTC(NIST) * @@ -101,18 +101,16 @@ like the `netcat` command on Unix. For example the request made with `tcp` above is equivalent to this: - > socket time.nist.gov 13 --read-only + > socket time.nist.gov:13 --read-only 59710 22-05-11 21:44:52 50 0 0 359.3 UTC(NIST) * And the request made with `http` is equivalent to that: - > socket moros.cc 80 --prompt - MOROS Socket v0.1.0 + > socket moros.cc:80 + GET /test.html HTTP/1.0 + Host: moros.cc - > GET /test.html HTTP/1.0 - > Host: moros.cc - > HTTP/1.1 200 OK Server: nginx Date: Wed, 11 May 2022 21:46:34 GMT @@ -136,27 +134,24 @@ And the request made with `http` is equivalent to that: Here's a connexion to a SMTP server to send a mail: - > socket 10.0.2.2 2500 --prompt - MOROS Socket v0.1.0 - + > socket 10.0.2.2:2500 220 EventMachine SMTP Server - > EHLO moros.cc + HELO moros.cc 250-Ok EventMachine SMTP Server - 250-NO-SOLICITING - 250 SIZE 20000000 - > MAIL FROM: - > RCPT TO: + MAIL FROM: 250 Ok + RCPT TO: 250 Ok - > DATA + DATA 354 Send it - > Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec - > diam vitae ex blandit malesuada nec a turpis. - > . - > QUIT + Subject: Test + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec + diam vitae ex blandit malesuada nec a turpis. + . 250 Message accepted + QUIT 221 Ok Sending a file to a server: - > socket 10.0.2.2 1234 <= /tmp/alice.txt + > socket 10.0.2.2:1234 <= /tmp/alice.txt diff --git a/doc/network.png b/doc/network.png index 8586165e6..d7a7cc767 100644 Binary files a/doc/network.png and b/doc/network.png differ diff --git a/doc/shell.md b/doc/shell.md index a31f1a9db..29ec303fa 100644 --- a/doc/shell.md +++ b/doc/shell.md @@ -100,15 +100,15 @@ file while the standard error is kept: > time read foo.txt => /dev/null The standard output is implied as the source of a redirection, but it is -possible to explicitly redirect a file handle to another (TODO): +possible to explicitly redirect a handle to another (TODO): > time read foo.txt [1]=>[3] -Or to redirect a file handle to a file: +Or to redirect a handle to a file: > time read foo.txt [1]=> bar.txt -Or to pipe a file handle to another command: +Or to pipe a handle to another command: > time read foo.txt [1]-> write bar.txt @@ -125,7 +125,7 @@ Redirections should be declared before piping (TODO): > write <= req.txt => /net/http/moros.cc -> find --line href -> sort -NOTE: The following file handles are available when a process is created: +NOTE: The following handles are available when a process is created: - `stdin(0)` - `stdout(1)` diff --git a/doc/syscalls.md b/doc/syscalls.md index 34497c164..deed4d037 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -69,3 +69,21 @@ The system will reboot with `0xcafe` and halt with `0xdead`. ```rust pub fn sleep(seconds: f64) ``` + +## CONNECT (0xC) + +```rust +pub fn connect(handle, usize, addr: &str, port: u16) -> isize +``` + +## LISTEN (0xD) + +```rust +pub fn listen(handle, usize, port: u16) -> isize +``` + +## ACCEPT (0xE) + +```rust +pub fn accept(handle, usize, addr: &str) -> isize +``` diff --git a/src/api/fs.rs b/src/api/fs.rs index 538303eae..aaf15f45d 100644 --- a/src/api/fs.rs +++ b/src/api/fs.rs @@ -9,9 +9,14 @@ use alloc::vec; pub use crate::sys::fs::{FileInfo, DeviceType}; +#[derive(Clone, Copy)] +pub enum IO { Read, Write } + pub trait FileIO { fn read(&mut self, buf: &mut [u8]) -> Result; fn write(&mut self, buf: &[u8]) -> Result; + fn close(&mut self); + fn poll(&mut self, event: IO) -> bool; } pub fn dirname(pathname: &str) -> &str { diff --git a/src/api/syscall.rs b/src/api/syscall.rs index 6d2faa4cd..20d98fd61 100644 --- a/src/api/syscall.rs +++ b/src/api/syscall.rs @@ -1,8 +1,12 @@ +use crate::api::fs::IO; use crate::api::process::ExitCode; use crate::syscall; use crate::sys::syscall::number::*; use crate::sys::fs::FileInfo; +use smoltcp::wire::IpAddress; +use smoltcp::wire::Ipv4Address; + pub fn exit(code: ExitCode) { unsafe { syscall!(EXIT, code as usize) }; } @@ -106,6 +110,51 @@ pub fn halt() { stop(0xdead); } +pub fn poll(list: &[(usize, IO)]) -> Option<(usize, IO)> { + let ptr = list.as_ptr() as usize; + let len = list.len(); + let idx = unsafe { syscall!(POLL, ptr, len) } as isize; + if 0 <= idx && idx < len as isize { + Some(list[idx as usize]) + } else { + None + } +} + +pub fn connect(handle: usize, addr: IpAddress, port: u16) -> Result<(), ()> { + let buf = addr.as_bytes(); + let ptr = buf.as_ptr() as usize; + let len = buf.len(); + let res = unsafe { syscall!(CONNECT, handle, ptr, len, port) } as isize; + if res >= 0 { + Ok(()) + } else { + Err(()) + } +} + +pub fn listen(handle: usize, port: u16) -> Result<(), ()> { + let res = unsafe { syscall!(LISTEN, handle, port) } as isize; + if res >= 0 { + Ok(()) + } else { + Err(()) + } +} + +pub fn accept(handle: usize) -> Result { + let addr = IpAddress::v4(0, 0, 0, 0); + let buf = addr.as_bytes(); + let ptr = buf.as_ptr() as usize; + let len = buf.len(); + let res = unsafe { syscall!(ACCEPT, handle, ptr, len) } as isize; + if res >= 0 { + Ok(IpAddress::from(Ipv4Address::from_bytes(buf))) + } else { + Err(()) + } +} + #[test_case] fn test_file() { use crate::sys::fs::{mount_mem, format_mem, dismount, OpenFlag}; diff --git a/src/sys/clock.rs b/src/sys/clock.rs index 3edad8b0b..ba9b834c4 100644 --- a/src/sys/clock.rs +++ b/src/sys/clock.rs @@ -1,7 +1,7 @@ use crate::api::clock::DATE_TIME_ZONE; +use crate::api::fs::{FileIO, IO}; use crate::sys; use crate::sys::cmos::CMOS; -use crate::sys::fs::FileIO; use time::{OffsetDateTime, Duration}; @@ -31,9 +31,20 @@ impl FileIO for Uptime { Err(()) } } + fn write(&mut self, _buf: &[u8]) -> Result { unimplemented!(); } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => true, + IO::Write => false, + } + } } // NOTE: This clock is monotonic @@ -65,9 +76,20 @@ impl FileIO for Realtime { Err(()) } } + fn write(&mut self, _buf: &[u8]) -> Result { unimplemented!(); } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => true, + IO::Write => false, + } + } } // NOTE: This clock is not monotonic diff --git a/src/sys/cmos.rs b/src/sys/cmos.rs index 6c48b792e..072636710 100644 --- a/src/sys/cmos.rs +++ b/src/sys/cmos.rs @@ -1,5 +1,5 @@ use crate::api::clock::{DATE_TIME, DATE_TIME_LEN}; -use crate::sys::fs::FileIO; +use crate::api::fs::{FileIO, IO}; use alloc::string::String; use bit_field::BitField; @@ -83,6 +83,16 @@ impl FileIO for RTC { CMOS::new().update_rtc(self); Ok(buf.len()) } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => true, + IO::Write => true, + } + } } pub struct CMOS { diff --git a/src/sys/console.rs b/src/sys/console.rs index bff596141..6966480e6 100644 --- a/src/sys/console.rs +++ b/src/sys/console.rs @@ -1,5 +1,5 @@ +use crate::api::fs::{FileIO, IO}; use crate::sys; -use crate::sys::fs::FileIO; use alloc::string::String; use alloc::string::ToString; use core::fmt; @@ -43,6 +43,16 @@ impl FileIO for Console { print_fmt(format_args!("{}", s)); Ok(n) } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => STDIN.lock().contains('\n'), + IO::Write => true, + } + } } pub fn cols() -> usize { diff --git a/src/sys/fs/device.rs b/src/sys/fs/device.rs index b5d81bccc..c24f5cd16 100644 --- a/src/sys/fs/device.rs +++ b/src/sys/fs/device.rs @@ -1,4 +1,4 @@ -use super::{dirname, filename, realpath, FileIO}; +use super::{dirname, filename, realpath, FileIO, IO}; use super::dir::Dir; use super::file::File; use super::block::LinkedBlock; @@ -7,6 +7,8 @@ use crate::sys::cmos::RTC; use crate::sys::console::Console; use crate::sys::random::Random; use crate::sys::clock::{Uptime, Realtime}; +use crate::sys::net::socket::tcp::TcpSocket; +use crate::sys::net::socket::udp::UdpSocket; use alloc::vec; use alloc::vec::Vec; @@ -14,24 +16,28 @@ use alloc::vec::Vec; #[derive(PartialEq, Eq, Clone, Copy)] #[repr(u8)] pub enum DeviceType { - Null = 0, - File = 1, - Console = 2, - Random = 3, - Uptime = 4, - Realtime = 5, - RTC = 6, + Null = 0, + File = 1, + Console = 2, + Random = 3, + Uptime = 4, + Realtime = 5, + RTC = 6, + TcpSocket = 7, + UdpSocket = 8, } // Used when creating a device impl DeviceType { pub fn buf(self) -> Vec { let len = match self { - DeviceType::RTC => RTC::size(), - DeviceType::Uptime => Uptime::size(), - DeviceType::Realtime => Realtime::size(), - DeviceType::Console => Console::size(), - _ => 1, + DeviceType::RTC => RTC::size(), + DeviceType::Uptime => Uptime::size(), + DeviceType::Realtime => Realtime::size(), + DeviceType::Console => Console::size(), + DeviceType::TcpSocket => TcpSocket::size(), + DeviceType::UdpSocket => UdpSocket::size(), + _ => 1, }; let mut res = vec![0; len]; res[0] = self as u8; @@ -48,6 +54,8 @@ pub enum Device { Uptime(Uptime), Realtime(Realtime), RTC(RTC), + TcpSocket(TcpSocket), + UdpSocket(UdpSocket), } impl From for Device { @@ -60,6 +68,8 @@ impl From for Device { i if i == DeviceType::Uptime as u8 => Device::Uptime(Uptime::new()), i if i == DeviceType::Realtime as u8 => Device::Realtime(Realtime::new()), i if i == DeviceType::RTC as u8 => Device::RTC(RTC::new()), + i if i == DeviceType::TcpSocket as u8 => Device::TcpSocket(TcpSocket::new()), + i if i == DeviceType::UdpSocket as u8 => Device::UdpSocket(UdpSocket::new()), _ => unimplemented!(), } } @@ -100,25 +110,57 @@ impl Device { impl FileIO for Device { fn read(&mut self, buf: &mut [u8]) -> Result { match self { - Device::Null => Err(()), - Device::File(io) => io.read(buf), - Device::Console(io) => io.read(buf), - Device::Random(io) => io.read(buf), - Device::Uptime(io) => io.read(buf), - Device::Realtime(io) => io.read(buf), - Device::RTC(io) => io.read(buf), + Device::Null => Err(()), + Device::File(io) => io.read(buf), + Device::Console(io) => io.read(buf), + Device::Random(io) => io.read(buf), + Device::Uptime(io) => io.read(buf), + Device::Realtime(io) => io.read(buf), + Device::RTC(io) => io.read(buf), + Device::TcpSocket(io) => io.read(buf), + Device::UdpSocket(io) => io.read(buf), } } fn write(&mut self, buf: &[u8]) -> Result { match self { - Device::Null => Ok(0), - Device::File(io) => io.write(buf), - Device::Console(io) => io.write(buf), - Device::Random(io) => io.write(buf), - Device::Uptime(io) => io.write(buf), - Device::Realtime(io) => io.write(buf), - Device::RTC(io) => io.write(buf), + Device::Null => Ok(0), + Device::File(io) => io.write(buf), + Device::Console(io) => io.write(buf), + Device::Random(io) => io.write(buf), + Device::Uptime(io) => io.write(buf), + Device::Realtime(io) => io.write(buf), + Device::RTC(io) => io.write(buf), + Device::TcpSocket(io) => io.write(buf), + Device::UdpSocket(io) => io.write(buf), + } + } + + fn close(&mut self) { + match self { + Device::Null => {}, + Device::File(io) => io.close(), + Device::Console(io) => io.close(), + Device::Random(io) => io.close(), + Device::Uptime(io) => io.close(), + Device::Realtime(io) => io.close(), + Device::RTC(io) => io.close(), + Device::TcpSocket(io) => io.close(), + Device::UdpSocket(io) => io.close(), + } + } + + fn poll(&mut self, event: IO) -> bool { + match self { + Device::Null => false, + Device::File(io) => io.poll(event), + Device::Console(io) => io.poll(event), + Device::Random(io) => io.poll(event), + Device::Uptime(io) => io.poll(event), + Device::Realtime(io) => io.poll(event), + Device::RTC(io) => io.poll(event), + Device::TcpSocket(io) => io.poll(event), + Device::UdpSocket(io) => io.poll(event), } } } diff --git a/src/sys/fs/dir.rs b/src/sys/fs/dir.rs index 6d7f4f977..0dc59fdab 100644 --- a/src/sys/fs/dir.rs +++ b/src/sys/fs/dir.rs @@ -1,4 +1,4 @@ -use super::{dirname, filename, realpath, FileIO}; +use super::{dirname, filename, realpath, FileIO, IO}; use super::super_block::SuperBlock; use super::dir_entry::DirEntry; use super::read_dir::ReadDir; @@ -239,9 +239,20 @@ impl FileIO for Dir { } Ok(i) } + fn write(&mut self, _buf: &[u8]) -> Result { Err(()) } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => self.entry_index < self.entries().count() as u32, + IO::Write => true, + } + } } // Truncate to the given number of bytes at most while respecting char boundaries diff --git a/src/sys/fs/file.rs b/src/sys/fs/file.rs index 5a6564c72..c825d121d 100644 --- a/src/sys/fs/file.rs +++ b/src/sys/fs/file.rs @@ -1,4 +1,4 @@ -use super::{dirname, filename, realpath, FileIO}; +use super::{dirname, filename, realpath, FileIO, IO}; use super::dir::Dir; use super::block::LinkedBlock; use super::dir_entry::DirEntry; @@ -199,6 +199,16 @@ impl FileIO for File { } Ok(bytes) } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => self.offset < self.size, + IO::Write => true, + } + } } #[test_case] diff --git a/src/sys/fs/mod.rs b/src/sys/fs/mod.rs index 99e5459c1..4bafe8e0a 100644 --- a/src/sys/fs/mod.rs +++ b/src/sys/fs/mod.rs @@ -16,7 +16,7 @@ pub use dir::Dir; pub use dir_entry::FileInfo; pub use file::{File, SeekFrom}; pub use block_device::{format_ata, format_mem, is_mounted, mount_ata, mount_mem, dismount}; -pub use crate::api::fs::{dirname, filename, realpath, FileIO}; +pub use crate::api::fs::{dirname, filename, realpath, FileIO, IO}; pub use crate::sys::ata::BLOCK_SIZE; use dir_entry::DirEntry; @@ -26,6 +26,7 @@ use alloc::string::{String, ToString}; pub const VERSION: u8 = 1; +// TODO: Move that to API #[derive(Clone, Copy)] #[repr(u8)] pub enum OpenFlag { @@ -76,10 +77,10 @@ pub fn open(path: &str, flags: usize) -> Option { pub fn delete(path: &str) -> Result<(), ()> { if let Some(info) = info(path) { - if info.is_file() { - return File::delete(path); - } else if info.is_dir() { + if info.is_dir() { return Dir::delete(path); + } else if info.is_file() || info.is_device() { + return File::delete(path); } } Err(()) @@ -122,6 +123,22 @@ impl FileIO for Resource { Resource::Device(io) => io.write(buf), } } + + fn close(&mut self) { + match self { + Resource::Dir(io) => io.close(), + Resource::File(io) => io.close(), + Resource::Device(io) => io.close(), + } + } + + fn poll(&mut self, event: IO) -> bool { + match self { + Resource::Dir(io) => io.poll(event), + Resource::File(io) => io.poll(event), + Resource::Device(io) => io.poll(event), + } + } } pub fn canonicalize(path: &str) -> Result { diff --git a/src/sys/net/mod.rs b/src/sys/net/mod.rs index 8c395aa4a..e7d089d8c 100644 --- a/src/sys/net/mod.rs +++ b/src/sys/net/mod.rs @@ -1,3 +1,6 @@ +mod nic; +pub mod socket; + use crate::{sys, usr}; use alloc::sync::Arc; @@ -9,15 +12,27 @@ use smoltcp::time::Instant; use smoltcp::wire::EthernetAddress; use spin::Mutex; -mod rtl8139; -mod pcnet; - pub static NET: Mutex> = Mutex::new(None); +#[repr(u8)] +pub enum SocketStatus { + IsListening = 0, + IsActive = 1, + IsOpen = 2, + CanSend = 3, + MaySend = 4, + CanRecv = 5, + MayRecv = 6, +} + +fn time() -> Instant { + Instant::from_micros((sys::clock::realtime() * 1000000.0) as i64) +} + #[derive(Clone)] pub enum EthernetDevice { - RTL8139(rtl8139::Device), - PCNET(pcnet::Device), + RTL8139(nic::rtl8139::Device), + PCNET(nic::pcnet::Device), //E2000, //VirtIO, } @@ -223,16 +238,15 @@ pub fn init() { log!("NET {} MAC {}\n", name, mac); let config = smoltcp::iface::Config::new(mac.into()); - let time = Instant::from_micros((sys::clock::realtime() * 1000000.0) as i64); - let iface = Interface::new(config, &mut device, time); + let iface = Interface::new(config, &mut device, time()); *NET.lock() = Some((iface, device)); } }; if let Some(io_base) = find_pci_io_base(0x10EC, 0x8139) { - add(EthernetDevice::RTL8139(rtl8139::Device::new(io_base)), "RTL8139"); + add(EthernetDevice::RTL8139(nic::rtl8139::Device::new(io_base)), "RTL8139"); } if let Some(io_base) = find_pci_io_base(0x1022, 0x2000) { - add(EthernetDevice::PCNET(pcnet::Device::new(io_base)), "PCNET"); + add(EthernetDevice::PCNET(nic::pcnet::Device::new(io_base)), "PCNET"); } } diff --git a/src/sys/net/nic/mod.rs b/src/sys/net/nic/mod.rs new file mode 100644 index 000000000..da2f57b93 --- /dev/null +++ b/src/sys/net/nic/mod.rs @@ -0,0 +1,2 @@ +pub mod rtl8139; +pub mod pcnet; diff --git a/src/sys/net/pcnet.rs b/src/sys/net/nic/pcnet.rs similarity index 100% rename from src/sys/net/pcnet.rs rename to src/sys/net/nic/pcnet.rs diff --git a/src/sys/net/rtl8139.rs b/src/sys/net/nic/rtl8139.rs similarity index 100% rename from src/sys/net/rtl8139.rs rename to src/sys/net/nic/rtl8139.rs diff --git a/src/sys/net/socket/mod.rs b/src/sys/net/socket/mod.rs new file mode 100644 index 000000000..0842ead48 --- /dev/null +++ b/src/sys/net/socket/mod.rs @@ -0,0 +1,22 @@ +use crate::sys; + +pub mod tcp; +pub mod udp; + +use alloc::vec; +use lazy_static::lazy_static; +use smoltcp::iface::SocketSet; +use smoltcp::time::Duration; +use spin::Mutex; + +lazy_static! { + pub static ref SOCKETS: Mutex> = Mutex::new(SocketSet::new(vec![])); +} + +fn random_port() -> u16 { + 49152 + sys::random::get_u16() % 16384 +} + +fn wait(duration: Duration) { + sys::time::sleep((duration.total_micros() as f64) / 1000000.0); +} diff --git a/src/sys/net/socket/tcp.rs b/src/sys/net/socket/tcp.rs new file mode 100644 index 000000000..2dc5c4955 --- /dev/null +++ b/src/sys/net/socket/tcp.rs @@ -0,0 +1,254 @@ +use crate::sys; + +use crate::api::fs::{FileIO, IO}; +use crate::sys::net::SocketStatus; + +use super::SOCKETS; +use super::{random_port, wait}; + +use alloc::vec; +use bit_field::BitField; +use smoltcp::iface::SocketHandle; +use smoltcp::phy::Device; +use smoltcp::socket::tcp; +use smoltcp::wire::IpAddress; + +fn tcp_socket_status(socket: &tcp::Socket) -> u8 { + let mut status = 0; + status.set_bit(SocketStatus::IsListening as usize, socket.is_listening()); + status.set_bit(SocketStatus::IsActive as usize, socket.is_active()); + status.set_bit(SocketStatus::IsOpen as usize, socket.is_open()); + status.set_bit(SocketStatus::MaySend as usize, socket.may_send()); + status.set_bit(SocketStatus::CanSend as usize, socket.can_send()); + status.set_bit(SocketStatus::MayRecv as usize, socket.may_recv()); + status.set_bit(SocketStatus::CanRecv as usize, socket.can_recv()); + status +} + +#[derive(Debug, Clone)] +pub struct TcpSocket { + pub handle: SocketHandle, +} + +impl TcpSocket { + pub fn size() -> usize { + if let Some((_, ref mut device)) = *sys::net::NET.lock() { + let mtu = device.capabilities().max_transmission_unit; + let eth_header = 14; + let ip_header = 20; + let tcp_header = 20; + mtu - eth_header - ip_header - tcp_header + } else { + 1 + } + } + + pub fn new() -> Self { + let mut sockets = SOCKETS.lock(); + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let handle = sockets.add(tcp_socket); + + Self { handle } + } + + pub fn connect(&mut self, addr: IpAddress, port: u16) -> Result<(), ()> { + let mut connecting = false; + let timeout = 5.0; + let started = sys::clock::realtime(); + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + loop { + if sys::clock::realtime() - started > timeout { + return Err(()); + } + let mut sockets = SOCKETS.lock(); + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + match socket.state() { + tcp::State::Closed => { + if connecting { + return Err(()); + } + let cx = iface.context(); + let dest = (addr, port); + if socket.connect(cx, dest, random_port()).is_err() { + return Err(()); + } + connecting = true; + } + tcp::State::SynSent => { + } + tcp::State::Established => { + break; + } + _ => { + // Did something get sent before the connection closed? + return if socket.can_recv() { + Ok(()) + } else { + Err(()) + }; + } + } + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + } + Ok(()) + } + + pub fn listen(&mut self, port: u16) -> Result<(), ()> { + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if socket.listen(port).is_err() { + return Err(()); + } + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + Ok(()) + } else { + Err(()) + } + } + + pub fn accept(&mut self) -> Result { + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + loop { + let mut sockets = SOCKETS.lock(); + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if let Some(endpoint) = socket.remote_endpoint() { + return Ok(endpoint.addr); + } + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + } else { + Err(()) + } + } +} + +impl FileIO for TcpSocket { + fn read(&mut self, buf: &mut [u8]) -> Result { + let timeout = 5.0; + let started = sys::clock::realtime(); + let mut bytes = 0; + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + loop { + if sys::clock::realtime() - started > timeout { + return Err(()); + } + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if buf.len() == 1 { // 1 byte status read + buf[0] = tcp_socket_status(socket); + return Ok(1); + } + + if socket.can_recv() { + bytes = socket.recv_slice(buf).map_err(|_| ())?; + break; + } + if !socket.may_recv() { + break; + } + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + Ok(bytes) + } else { + Err(()) + } + } + + fn write(&mut self, buf: &[u8]) -> Result { + let timeout = 5.0; + let started = sys::clock::realtime(); + let mut sent = false; + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + loop { + if sys::clock::realtime() - started > timeout { + return Err(()); + } + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if sent { + break; + } + if socket.can_send() { + if socket.send_slice(buf.as_ref()).is_err() { + return Err(()); + } + sent = true; // Break after next poll + } + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + Ok(buf.len()) + } else { + Err(()) + } + } + + fn close(&mut self) { + let mut closed = false; + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + loop { + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if closed { + break; + } + socket.close(); + closed = true; + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + } + } + + fn poll(&mut self, event: IO) -> bool { + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + match event { + IO::Read => socket.can_recv(), + IO::Write => socket.can_send(), + } + } else { + false + } + } +} diff --git a/src/sys/net/socket/udp.rs b/src/sys/net/socket/udp.rs new file mode 100644 index 000000000..0e2be7cc4 --- /dev/null +++ b/src/sys/net/socket/udp.rs @@ -0,0 +1,199 @@ +use crate::sys; + +use crate::api::fs::{FileIO, IO}; +use crate::sys::net::SocketStatus; + +use super::SOCKETS; +use super::{random_port, wait}; + +use alloc::vec; +use bit_field::BitField; +use smoltcp::iface::SocketHandle; +use smoltcp::phy::Device; +use smoltcp::socket::udp; +use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint}; + +fn udp_socket_status(socket: &udp::Socket) -> u8 { + let mut status = 0; + status.set_bit(SocketStatus::IsOpen as usize, socket.is_open()); + status.set_bit(SocketStatus::CanSend as usize, socket.can_send()); + status.set_bit(SocketStatus::CanRecv as usize, socket.can_recv()); + status +} + +#[derive(Debug, Clone)] +pub struct UdpSocket { + pub handle: SocketHandle, + pub remote_endpoint: Option, +} + +impl UdpSocket { + pub fn size() -> usize { + if let Some((_, ref mut device)) = *sys::net::NET.lock() { + let mtu = device.capabilities().max_transmission_unit; + let eth_header = 14; + let ip_header = 20; + let udp_header = 8; + mtu - eth_header - ip_header - udp_header + } else { + 1 + } + } + + pub fn new() -> Self { + let mut sockets = SOCKETS.lock(); + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let handle = sockets.add(udp_socket); + let remote_endpoint = None; + + Self { handle, remote_endpoint } + } + + pub fn connect(&mut self, addr: IpAddress, port: u16) -> Result<(), ()> { + let timeout = 5.0; + let started = sys::clock::realtime(); + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + loop { + if sys::clock::realtime() - started > timeout { + return Err(()); + } + let mut sockets = SOCKETS.lock(); + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if !socket.is_open() { + let local_endpoint = IpListenEndpoint::from(random_port()); + socket.bind(local_endpoint).unwrap(); + break; + } + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + } + self.remote_endpoint = Some(IpEndpoint::new(addr, port)); + Ok(()) + } + + pub fn listen(&mut self, _port: u16) -> Result<(), ()> { + todo!() + } + + pub fn accept(&mut self) -> Result { + todo!() + } +} + +impl FileIO for UdpSocket { + fn read(&mut self, buf: &mut [u8]) -> Result { + let timeout = 5.0; + let started = sys::clock::realtime(); + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let bytes; + let mut sockets = SOCKETS.lock(); + loop { + if sys::clock::realtime() - started > timeout { + return Err(()); + } + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if buf.len() == 1 { // 1 byte status read + buf[0] = udp_socket_status(socket); + return Ok(1); + } + + if socket.can_recv() { + (bytes, _) = socket.recv_slice(buf).map_err(|_| ())?; + break; + } + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + Ok(bytes) + } else { + Err(()) + } + } + + fn write(&mut self, buf: &[u8]) -> Result { + let timeout = 5.0; + let started = sys::clock::realtime(); + let mut sent = false; + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + loop { + if sys::clock::realtime() - started > timeout { + return Err(()); + } + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if sent { + break; + } + if socket.can_send() { + if let Some(remote_endpoint) = self.remote_endpoint { + if socket.send_slice(buf.as_ref(), remote_endpoint).is_err() { + return Err(()); + } + } else { + return Err(()); + } + sent = true; // Break after next poll + } + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + Ok(buf.len()) + } else { + Err(()) + } + } + + fn close(&mut self) { + let mut closed = false; + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + loop { + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + if closed { + break; + } + socket.close(); + closed = true; + + if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) { + wait(duration); + } + sys::time::halt(); + } + } + } + + fn poll(&mut self, event: IO) -> bool { + if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { + let mut sockets = SOCKETS.lock(); + iface.poll(sys::net::time(), device, &mut sockets); + let socket = sockets.get_mut::(self.handle); + + match event { + IO::Read => socket.can_recv(), + IO::Write => socket.can_send(), + } + } else { + false + } + } +} diff --git a/src/sys/process.rs b/src/sys/process.rs index 365d62ad1..a52f1a064 100644 --- a/src/sys/process.rs +++ b/src/sys/process.rs @@ -13,7 +13,7 @@ use object::{Object, ObjectSegment}; use spin::RwLock; use x86_64::structures::idt::InterruptStackFrameValue; -const MAX_FILE_HANDLES: usize = 64; +const MAX_HANDLES: usize = 64; const MAX_PROCS: usize = 2; // TODO: Update this when more than one process can run at once const MAX_PROC_SIZE: usize = 10 << 20; // 10 MB @@ -29,7 +29,7 @@ pub struct ProcessData { env: BTreeMap, dir: String, user: Option, - file_handles: [Option>; MAX_FILE_HANDLES], + handles: [Option>; MAX_HANDLES], } impl ProcessData { @@ -37,12 +37,12 @@ impl ProcessData { let env = BTreeMap::new(); let dir = dir.to_string(); let user = user.map(String::from); - let mut file_handles = [(); MAX_FILE_HANDLES].map(|_| None); - file_handles[0] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdin - file_handles[1] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdout - file_handles[2] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stderr - file_handles[3] = Some(Box::new(Resource::Device(Device::Null))); // stdnull - Self { env, dir, user, file_handles } + let mut handles = [(); MAX_HANDLES].map(|_| None); + handles[0] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdin + handles[1] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdout + handles[2] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stderr + handles[3] = Some(Box::new(Resource::Device(Device::Null))); // stdnull + Self { env, dir, user, handles } } } @@ -96,43 +96,43 @@ pub fn set_user(user: &str) { proc.data.user = Some(user.into()) } -pub fn create_file_handle(file: Resource) -> Result { +pub fn create_handle(file: Resource) -> Result { let mut table = PROCESS_TABLE.write(); let proc = &mut table[id()]; - let min = 4; // The first 4 file handles are reserved - let max = MAX_FILE_HANDLES; + let min = 4; // The first 4 handles are reserved + let max = MAX_HANDLES; for handle in min..max { - if proc.data.file_handles[handle].is_none() { - proc.data.file_handles[handle] = Some(Box::new(file)); + if proc.data.handles[handle].is_none() { + proc.data.handles[handle] = Some(Box::new(file)); return Ok(handle); } } - debug!("Could not create file handle"); + debug!("Could not create handle"); Err(()) } -pub fn update_file_handle(handle: usize, file: Resource) { +pub fn update_handle(handle: usize, file: Resource) { let mut table = PROCESS_TABLE.write(); let proc = &mut table[id()]; - proc.data.file_handles[handle] = Some(Box::new(file)); + proc.data.handles[handle] = Some(Box::new(file)); } -pub fn delete_file_handle(handle: usize) { +pub fn delete_handle(handle: usize) { let mut table = PROCESS_TABLE.write(); let proc = &mut table[id()]; - proc.data.file_handles[handle] = None; + proc.data.handles[handle] = None; } -pub fn file_handle(handle: usize) -> Option> { +pub fn handle(handle: usize) -> Option> { let table = PROCESS_TABLE.read(); let proc = &table[id()]; - proc.data.file_handles[handle].clone() + proc.data.handles[handle].clone() } -pub fn file_handles() -> Vec>> { +pub fn handles() -> Vec>> { let table = PROCESS_TABLE.read(); let proc = &table[id()]; - proc.data.file_handles.to_vec() + proc.data.handles.to_vec() } pub fn code_addr() -> u64 { diff --git a/src/sys/random.rs b/src/sys/random.rs index db9562b7d..0e8e41758 100644 --- a/src/sys/random.rs +++ b/src/sys/random.rs @@ -1,5 +1,5 @@ use crate::sys; -use crate::sys::fs::FileIO; +use crate::api::fs::{FileIO, IO}; use rand::{RngCore, SeedableRng}; use rand_hc::Hc128Rng; @@ -22,9 +22,20 @@ impl FileIO for Random { } Ok(n) } + fn write(&mut self, _buf: &[u8]) -> Result { unimplemented!(); } + + fn close(&mut self) { + } + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => true, + IO::Write => false, + } + } } pub fn get_u64() -> u64 { diff --git a/src/sys/syscall/mod.rs b/src/sys/syscall/mod.rs index b7f24b2ab..ec265aae9 100644 --- a/src/sys/syscall/mod.rs +++ b/src/sys/syscall/mod.rs @@ -3,9 +3,11 @@ pub mod service; use crate::api::process::ExitCode; use crate::sys; -use crate::sys::fs::FileInfo; +use crate::sys::fs::{FileInfo, IO}; use core::arch::asm; +use smoltcp::wire::IpAddress; +use smoltcp::wire::Ipv4Address; /* * Dispatching system calls @@ -68,14 +70,45 @@ pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize) let path_ptr = sys::process::ptr_from_addr(arg1 as u64); let path_len = arg2; let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(path_ptr, path_len)) }; - let args_ptr = arg3; let args_len = arg4; - service::spawn(path, args_ptr, args_len) as usize } number::STOP => { - service::stop(arg1) + let code = arg1; + service::stop(code) + } + number::POLL => { + let ptr = sys::process::ptr_from_addr(arg1 as u64) as *const (usize, IO); + let len = arg2; + let list = unsafe { core::slice::from_raw_parts(ptr, len) }; + service::poll(list) as usize + } + number::CONNECT => { + let handle = arg1; + let buf_ptr = sys::process::ptr_from_addr(arg2 as u64); + let buf_len = arg3; + let buf = unsafe { core::slice::from_raw_parts(buf_ptr, buf_len) }; + let addr = IpAddress::from(Ipv4Address::from_bytes(buf)); + let port = arg4 as u16; + service::connect(handle, addr, port) as usize + } + number::LISTEN => { + let handle = arg1; + let port = arg2 as u16; + service::listen(handle, port) as usize + } + number::ACCEPT => { + let handle = arg1; + let buf_ptr = sys::process::ptr_from_addr(arg2 as u64); + let buf_len = arg3; + let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr, buf_len) }; + if let Ok(addr) = service::accept(handle) { + buf[0..buf_len].clone_from_slice(&addr.as_bytes()); + 0 + } else { + -1 as isize as usize + } } _ => { unimplemented!(); diff --git a/src/sys/syscall/number.rs b/src/sys/syscall/number.rs index b7b5c532c..96f34d0bf 100644 --- a/src/sys/syscall/number.rs +++ b/src/sys/syscall/number.rs @@ -9,3 +9,7 @@ pub const DUP: usize = 0x8; pub const DELETE: usize = 0x9; pub const STOP: usize = 0xA; pub const SLEEP: usize = 0xB; +pub const POLL: usize = 0xC; +pub const CONNECT: usize = 0xD; +pub const LISTEN: usize = 0xE; +pub const ACCEPT: usize = 0xF; diff --git a/src/sys/syscall/service.rs b/src/sys/syscall/service.rs index 54e08d784..b61ca739b 100644 --- a/src/sys/syscall/service.rs +++ b/src/sys/syscall/service.rs @@ -1,11 +1,14 @@ use crate::sys; use crate::api::process::ExitCode; +use crate::api::fs::{FileIO, IO}; use crate::sys::fs::FileInfo; -use crate::sys::fs::FileIO; +use crate::sys::fs::Resource; +use crate::sys::fs::Device; use crate::sys::process::Process; use alloc::vec; use core::arch::asm; +use smoltcp::wire::IpAddress; pub fn exit(code: ExitCode) -> ExitCode { sys::process::exit(); @@ -43,7 +46,7 @@ pub fn open(path: &str, flags: usize) -> isize { Err(_) => return -1, }; if let Some(resource) = sys::fs::open(&path, flags) { - if let Ok(handle) = sys::process::create_file_handle(resource) { + if let Ok(handle) = sys::process::create_handle(resource) { return handle as isize; } } @@ -51,17 +54,17 @@ pub fn open(path: &str, flags: usize) -> isize { } pub fn dup(old_handle: usize, new_handle: usize) -> isize { - if let Some(file) = sys::process::file_handle(old_handle) { - sys::process::update_file_handle(new_handle, *file); + if let Some(file) = sys::process::handle(old_handle) { + sys::process::update_handle(new_handle, *file); return new_handle as isize; } -1 } pub fn read(handle: usize, buf: &mut [u8]) -> isize { - if let Some(mut file) = sys::process::file_handle(handle) { + if let Some(mut file) = sys::process::handle(handle) { if let Ok(bytes) = file.read(buf) { - sys::process::update_file_handle(handle, *file); + sys::process::update_handle(handle, *file); return bytes as isize; } } @@ -69,9 +72,9 @@ pub fn read(handle: usize, buf: &mut [u8]) -> isize { } pub fn write(handle: usize, buf: &mut [u8]) -> isize { - if let Some(mut file) = sys::process::file_handle(handle) { + if let Some(mut file) = sys::process::handle(handle) { if let Ok(bytes) = file.write(buf) { - sys::process::update_file_handle(handle, *file); + sys::process::update_handle(handle, *file); return bytes as isize; } } @@ -79,7 +82,10 @@ pub fn write(handle: usize, buf: &mut [u8]) -> isize { } pub fn close(handle: usize) { - sys::process::delete_file_handle(handle); + if let Some(mut file) = sys::process::handle(handle) { + file.close(); + sys::process::delete_handle(handle); + } } pub fn spawn(path: &str, args_ptr: usize, args_len: usize) -> ExitCode { @@ -124,3 +130,54 @@ pub fn stop(code: usize) -> usize { } 0 } + +pub fn poll(list: &[(usize, IO)]) -> isize { + for (i, (handle, event)) in list.iter().enumerate() { + if let Some(mut file) = sys::process::handle(*handle) { + if file.poll(*event) { + return i as isize; + } + } + } + -1 +} + +pub fn connect(handle: usize, addr: IpAddress, port: u16) -> isize { + if let Some(mut file) = sys::process::handle(handle) { + let res = match *file { + Resource::Device(Device::TcpSocket(ref mut dev)) => dev.connect(addr, port), + Resource::Device(Device::UdpSocket(ref mut dev)) => dev.connect(addr, port), + _ => Err(()), + }; + if res.is_ok() { + sys::process::update_handle(handle, *file); + return 0; + } + } + -1 +} + +pub fn listen(handle: usize, port: u16) -> isize { + if let Some(file) = sys::process::handle(handle) { + let res = match *file { + Resource::Device(Device::TcpSocket(mut dev)) => dev.listen(port), + Resource::Device(Device::UdpSocket(mut dev)) => dev.listen(port), + _ => Err(()), + }; + if res.is_ok() { + return 0; + } + } + -1 +} + +pub fn accept(handle: usize) -> Result { + if let Some(file) = sys::process::handle(handle) { + return match *file { + Resource::Device(Device::TcpSocket(mut dev)) => dev.accept(), + Resource::Device(Device::UdpSocket(mut dev)) => dev.accept(), + _ => Err(()), + }; + } + Err(()) +} diff --git a/src/usr/host.rs b/src/usr/host.rs index 9c6e20c7f..3fe8b0f61 100644 --- a/src/usr/host.rs +++ b/src/usr/host.rs @@ -1,6 +1,6 @@ -use crate::{sys, usr}; -use crate::api::clock; +use crate::usr; use crate::api::console::Style; +use crate::sys::fs::OpenFlag; use crate::api::process::ExitCode; use crate::api::random; use crate::api::syscall; @@ -10,10 +10,7 @@ use bit_field::BitField; use core::convert::TryInto; use core::str; use core::str::FromStr; -use smoltcp::iface::SocketSet; -use smoltcp::socket::udp; -use smoltcp::time::Instant; -use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint, Ipv4Address}; +use smoltcp::wire::{IpAddress, Ipv4Address}; // See RFC 1035 for implementation details @@ -106,13 +103,7 @@ impl Message { self.header().get_bit(15) } - /* - pub fn is_query(&self) -> bool { - !self.is_response() - } - */ - - pub fn rcode(&self) -> ResponseCode { + pub fn code(&self) -> ResponseCode { match self.header().get_bits(11..15) { 0 => ResponseCode::NoError, 1 => ResponseCode::FormatError, @@ -137,91 +128,58 @@ fn dns_address() -> Option { } pub fn resolve(name: &str) -> Result { - let dns_port = 53; - let dns_address = dns_address().unwrap_or(IpAddress::v4(8, 8, 8, 8)); - let server = IpEndpoint::new(dns_address, dns_port); - - let local_port = 49152 + random::get_u16() % 16384; - let client = IpListenEndpoint::from(local_port); - - let qname = name; - let qtype = QueryType::A; - let qclass = QueryClass::IN; - let query = Message::query(qname, qtype, qclass); - - #[derive(Debug)] - enum State { Bind, Query, Response } - let mut state = State::Bind; - - if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { - let mut sockets = SocketSet::new(vec![]); - let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]); - let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]); - let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); - let udp_handle = sockets.add(udp_socket); - - match iface.ipv4_addr() { - None => { - return Err(ResponseCode::NetworkError); - } - Some(ip_addr) if ip_addr.is_unspecified() => { - return Err(ResponseCode::NetworkError); - } - _ => {} - } + let addr = dns_address().unwrap_or(IpAddress::v4(8, 8, 8, 8)); + let port = 53; + let query = Message::query(name, QueryType::A, QueryClass::IN); - let timeout = 5.0; - let started = clock::realtime(); + let socket_path = "/dev/net/udp"; + let buf_len = if let Some(info) = syscall::info(socket_path) { + info.size() as usize + } else { + return Err(ResponseCode::NetworkError); + }; + + let flags = OpenFlag::Device as usize; + if let Some(handle) = syscall::open(socket_path, flags) { + if syscall::connect(handle, addr, port).is_err() { + syscall::close(handle); + return Err(ResponseCode::NetworkError) + } + if syscall::write(handle, &query.datagram).is_none() { + syscall::close(handle); + return Err(ResponseCode::NetworkError) + } loop { - if clock::realtime() - started > timeout { - return Err(ResponseCode::NetworkError); - } - - let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64); - iface.poll(time, device, &mut sockets); - let socket = sockets.get_mut::(udp_handle); - - state = match state { - State::Bind if !socket.is_open() => { - socket.bind(client).unwrap(); - State::Query - } - State::Query if socket.can_send() => { - socket.send_slice(&query.datagram, server).expect("cannot send"); - State::Response + let mut data = vec![0; buf_len]; + if let Some(bytes) = syscall::read(handle, &mut data) { + if bytes == 0 { + break; } - State::Response if socket.can_recv() => { - let (data, _) = socket.recv().expect("cannot receive"); - let message = Message::from(data); - if message.id() == query.id() && message.is_response() { - return match message.rcode() { - ResponseCode::NoError => { - // TODO: Parse the datagram instead of - // extracting the last 4 bytes. - //let rdata = message.answer().rdata(); - let n = message.datagram.len(); - let rdata = &message.datagram[(n - 4)..]; - - Ok(IpAddress::from(Ipv4Address::from_bytes(rdata))) - } - rcode => { - Err(rcode) - } + data.resize(bytes, 0); + + let message = Message::from(&data); + if message.id() == query.id() && message.is_response() { + syscall::close(handle); + return match message.code() { + ResponseCode::NoError => { + // TODO: Parse the datagram instead of extracting + // the last 4 bytes + let n = message.datagram.len(); + let data = &message.datagram[(n - 4)..]; + Ok(IpAddress::from(Ipv4Address::from_bytes(data))) + } + code => { + Err(code) } } - state } - _ => state - }; - - if let Some(wait_duration) = iface.poll_delay(time, &sockets) { - syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0); - + } else { + break; } } - } else { - Err(ResponseCode::NetworkError) + syscall::close(handle); } + Err(ResponseCode::NetworkError) } pub fn main(args: &[&str]) -> Result<(), ExitCode> { @@ -233,7 +191,7 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { let domain = args[1]; match resolve(domain) { Ok(addr) => { - println!("{} has address {}", domain, addr); + println!("{}", addr); Ok(()) } Err(e) => { diff --git a/src/usr/http.rs b/src/usr/http.rs index dde51b166..cd09686ff 100644 --- a/src/usr/http.rs +++ b/src/usr/http.rs @@ -1,15 +1,13 @@ use crate::{sys, usr}; use crate::api::console::Style; -use crate::api::clock; use crate::api::process::ExitCode; -use crate::api::random; use crate::api::syscall; +use crate::sys::fs::OpenFlag; + +use alloc::format; use alloc::string::{String, ToString}; use alloc::vec; use core::str::{self, FromStr}; -use smoltcp::iface::SocketSet; -use smoltcp::socket::tcp; -use smoltcp::time::Instant; use smoltcp::wire::IpAddress; #[derive(Debug)] @@ -19,7 +17,6 @@ struct URL { pub path: String, } -enum SessionState { Connect, Request, Response } enum ResponseState { Headers, Body } impl URL { @@ -103,9 +100,17 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { let url = "http://".to_string() + host + path; let url = URL::parse(&url).expect("invalid URL format"); - - let address = if url.host.ends_with(char::is_numeric) { - IpAddress::from_str(&url.host).expect("invalid address format") + let port = url.port; + let addr = if url.host.ends_with(char::is_numeric) { + match IpAddress::from_str(&url.host) { + Ok(ip_addr) => { + ip_addr + } + Err(_) => { + error!("Invalid address format"); + return Err(ExitCode::UsageError); + } + } } else { match usr::host::resolve(&url.host) { Ok(ip_addr) => { @@ -118,117 +123,92 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { } }; - let mut session_state = SessionState::Connect; + let socket_path = "/dev/net/tcp"; + let buf_len = if let Some(info) = syscall::info(socket_path) { + info.size() as usize + } else { + error!("Could not open '{}'", socket_path); + return Err(ExitCode::Failure); + }; - if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { - let mut sockets = SocketSet::new(vec![]); - let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); - let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); - let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - let tcp_handle = sockets.add(tcp_socket); + let flags = OpenFlag::Device as usize; + if let Some(handle) = syscall::open(socket_path, flags) { + if syscall::connect(handle, addr, port).is_err() { + error!("Could not connect to {}:{}", addr, port); + syscall::close(handle); + return Err(ExitCode::Failure); + } + let req = vec![ + format!("GET {} HTTP/1.1\r\n", url.path), + format!("Host: {}\r\n", url.host), + format!("User-Agent: MOROS/{}\r\n", env!("CARGO_PKG_VERSION")), + format!("Connection: close\r\n"), + format!("\r\n"), + ]; + if is_verbose { + print!("{}", csi_verbose); + for line in &req { + print!("> {}", line); + } + print!("{}", csi_reset); + } + let req = req.join(""); + syscall::write(handle, &req.as_bytes()); - let mut last_received_at = clock::realtime(); let mut response_state = ResponseState::Headers; loop { - if clock::realtime() - last_received_at > timeout { - error!("Timeout reached"); - return Err(ExitCode::Failure); - } if sys::console::end_of_text() || sys::console::end_of_transmission() { eprintln!(); + syscall::close(handle); return Err(ExitCode::Failure); } - - let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64); - iface.poll(time, device, &mut sockets); - let socket = sockets.get_mut::(tcp_handle); - let cx = iface.context(); - - session_state = match session_state { - SessionState::Connect if !socket.is_active() => { - let local_port = 49152 + random::get_u16() % 16384; - if is_verbose { - print!("{}", csi_verbose); - println!("* Connecting to {}:{}", address, url.port); - print!("{}", csi_reset); - } - if socket.connect(cx, (address, url.port), local_port).is_err() { - error!("Could not connect to {}:{}", address, url.port); - return Err(ExitCode::Failure); - } - SessionState::Request - } - SessionState::Request if socket.may_send() => { - let http_get = "GET ".to_string() + &url.path + " HTTP/1.1\r\n"; - let http_host = "Host: ".to_string() + &url.host + "\r\n"; - let http_ua = "User-Agent: MOROS/".to_string() + env!("CARGO_PKG_VERSION") + "\r\n"; - let http_connection = "Connection: close\r\n".to_string(); - if is_verbose { - print!("{}", csi_verbose); - print!("> {}", http_get); - print!("> {}", http_host); - print!("> {}", http_ua); - print!("> {}", http_connection); - println!(">"); - print!("{}", csi_reset); - } - socket.send_slice(http_get.as_ref()).expect("cannot send"); - socket.send_slice(http_host.as_ref()).expect("cannot send"); - socket.send_slice(http_ua.as_ref()).expect("cannot send"); - socket.send_slice(http_connection.as_ref()).expect("cannot send"); - socket.send_slice(b"\r\n").expect("cannot send"); - SessionState::Response + let mut data = vec![0; buf_len]; + if let Some(n) = syscall::read(handle, &mut data) { + if n == 0 { + break; } - SessionState::Response if socket.can_recv() => { - socket.recv(|data| { - last_received_at = clock::realtime(); - let n = data.len(); - let mut i = 0; - while i < n { - match response_state { - ResponseState::Headers => { - let mut j = i; - while j < n { - if data[j] == b'\n' { - break; - } - j += 1; - } - let line = String::from_utf8_lossy(&data[i..j]); // TODO: check i == j - if is_verbose { - if i == 0 { - print!("{}", csi_verbose); - } - println!("< {}", line); - } - if line.trim().is_empty() { - if is_verbose { - print!("{}", csi_reset); - } - response_state = ResponseState::Body; - } - i = j + 1; - } - ResponseState::Body => { - syscall::write(1, &data[i..n]); + data.resize(n, 0); + let mut i = 0; + while i < n { + match response_state { + ResponseState::Headers => { + let mut j = i; + while j < n { + if data[j] == b'\n' { break; } + j += 1; + } + let line = String::from_utf8_lossy(&data[i..j]); // TODO: check i == j + if is_verbose { + if i == 0 { + print!("{}", csi_verbose); + } + println!("< {}", line); } + if line.trim().is_empty() { + if is_verbose { + print!("{}", csi_reset); + } + response_state = ResponseState::Body; + } + i = j + 1; } - (data.len(), ()) - }).unwrap(); - SessionState::Response - } - SessionState::Response if !socket.may_recv() => { - break; + ResponseState::Body => { + // NOTE: The buffer may not be convertible to a UTF-8 string + // so we write it to STDOUT directly instead of using print. + syscall::write(1, &data[i..n]); + break; + } + } } - _ => session_state - }; - - if let Some(wait_duration) = iface.poll_delay(time, &sockets) { - syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0); + } else { + error!("Could not read from {}:{}", addr, port); + syscall::close(handle); + return Err(ExitCode::Failure); } } + syscall::close(handle); Ok(()) } else { Err(ExitCode::Failure) diff --git a/src/usr/install.rs b/src/usr/install.rs index 8c691c93a..9568e16df 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -27,13 +27,16 @@ pub fn copy_files(verbose: bool) { copy_file("/bin/reboot", include_bytes!("../../dsk/bin/reboot"), verbose); copy_file("/bin/sleep", include_bytes!("../../dsk/bin/sleep"), verbose); - create_dir("/dev/clk", verbose); // Clocks + create_dir("/dev/clk", verbose); // Clock create_dev("/dev/clk/uptime", DeviceType::Uptime, verbose); create_dev("/dev/clk/realtime", DeviceType::Realtime, verbose); create_dev("/dev/rtc", DeviceType::RTC, verbose); create_dev("/dev/null", DeviceType::Null, verbose); create_dev("/dev/random", DeviceType::Random, verbose); create_dev("/dev/console", DeviceType::Console, verbose); + create_dir("/dev/net", verbose); // Network + create_dev("/dev/net/tcp", DeviceType::TcpSocket, verbose); + create_dev("/dev/net/udp", DeviceType::UdpSocket, verbose); copy_file("/ini/banner.txt", include_bytes!("../../dsk/ini/banner.txt"), verbose); copy_file("/ini/boot.sh", include_bytes!("../../dsk/ini/boot.sh"), verbose); diff --git a/src/usr/lisp/env.rs b/src/usr/lisp/env.rs index 3611eebef..798d9790d 100644 --- a/src/usr/lisp/env.rs +++ b/src/usr/lisp/env.rs @@ -74,6 +74,9 @@ pub fn default_env() -> Rc> { data.insert("file.read".to_string(), Exp::Primitive(primitive::lisp_file_read)); data.insert("file.write".to_string(), Exp::Primitive(primitive::lisp_file_write)); data.insert("file.close".to_string(), Exp::Primitive(primitive::lisp_file_close)); + data.insert("socket.connect".to_string(), Exp::Primitive(primitive::lisp_socket_connect)); + data.insert("socket.listen".to_string(), Exp::Primitive(primitive::lisp_socket_listen)); + data.insert("socket.accept".to_string(), Exp::Primitive(primitive::lisp_socket_accept)); // Setup autocompletion *FUNCTIONS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect(); diff --git a/src/usr/lisp/primitive.rs b/src/usr/lisp/primitive.rs index 071b1b84b..f907ea148 100644 --- a/src/usr/lisp/primitive.rs +++ b/src/usr/lisp/primitive.rs @@ -17,6 +17,8 @@ use alloc::vec; use core::cmp::Ordering::Equal; use core::convert::TryFrom; use core::convert::TryInto; +use core::str::FromStr; +use smoltcp::wire::IpAddress; pub fn lisp_eq(args: &[Exp]) -> Result { Ok(Exp::Bool(numbers(args)?.windows(2).all(|nums| nums[0] == nums[1]))) @@ -477,3 +479,44 @@ pub fn lisp_file_write(args: &[Exp]) -> Result { _ => expected!("second argument to be a list") } } + +pub fn lisp_socket_connect(args: &[Exp]) -> Result { + ensure_length_eq!(args, 3); + let kind = string(&args[0])?; + let addr_str = string(&args[1])?; + let addr = match IpAddress::from_str(&addr_str) { + Ok(addr) => addr, + Err(()) => return expected!("valid IP address"), + }; + let port: usize = number(&args[2])?.try_into()?; + let flags = OpenFlag::Device as usize; + if let Some(handle) = syscall::open(&format!("/dev/net/{}", kind), flags) { + if syscall::connect(handle, addr, port as u16).is_ok() { + return Ok(Exp::Num(Number::from(handle))); + } + } + could_not!("connect to {}:{}", addr, port) +} + +pub fn lisp_socket_listen(args: &[Exp]) -> Result { + ensure_length_eq!(args, 2); + let kind = string(&args[0])?; + let port: usize = number(&args[1])?.try_into()?; + let flags = OpenFlag::Device as usize; + if let Some(handle) = syscall::open(&format!("/dev/net/{}", kind), flags) { + if syscall::listen(handle, port as u16).is_ok() { + return Ok(Exp::Num(Number::from(handle))); + } + } + could_not!("listen to 0.0.0.0:{}", port) +} + +pub fn lisp_socket_accept(args: &[Exp]) -> Result { + ensure_length_eq!(args, 1); + let handle: usize = number(&args[0])?.try_into()?; + if let Ok(addr) = syscall::accept(handle) { + Ok(Exp::Str(format!("{}", addr))) + } else { + could_not!("accept connections") + } +} diff --git a/src/usr/read.rs b/src/usr/read.rs index 43670df22..f7339fedb 100644 --- a/src/usr/read.rs +++ b/src/usr/read.rs @@ -6,6 +6,7 @@ use crate::api::syscall; use crate::api::process::ExitCode; use alloc::borrow::ToOwned; +use alloc::format; use alloc::vec::Vec; use core::convert::TryInto; @@ -28,6 +29,9 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { // TODO: Create device drivers for `/net` hardcoded commands if path.starts_with("/net/") { + let csi_option = Style::color("LightCyan"); + let csi_title = Style::color("Yellow"); + let csi_reset = Style::reset(); // Examples: // > read /net/http/example.com/articles // > read /net/http/example.com:8080/articles/index.html @@ -35,18 +39,25 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { // > read /net/tcp/time.nist.gov:13 let parts: Vec<_> = path.split('/').collect(); if parts.len() < 4 { - eprintln!("Usage: read /net/http//"); + println!("{}Usage:{} read {}/net//[:]/{1}", csi_title, csi_reset, csi_option); Err(ExitCode::Failure) } else { + let host = parts[3]; match parts[2] { "tcp" => { - let host = parts[3]; - usr::tcp::main(&["tcp", host]) + if host.contains(':') { + usr::tcp::main(&["tcp", host]) + } else { + error!("Missing port number"); + Err(ExitCode::Failure) + } } "daytime" => { - let host = parts[3]; - let port = "13"; - usr::tcp::main(&["tcp", host, port]) + if host.contains(':') { + usr::tcp::main(&["tcp", host]) + } else { + usr::tcp::main(&["tcp", &format!("{}:13", host)]) + } } "http" => { let host = parts[3]; diff --git a/src/usr/shell.rs b/src/usr/shell.rs index c27e6703a..c084783cb 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -252,7 +252,7 @@ fn cmd_proc(args: &[&str]) -> Result<(), ExitCode> { Ok(()) } "files" => { - for (i, handle) in sys::process::file_handles().iter().enumerate() { + for (i, handle) in sys::process::handles().iter().enumerate() { if let Some(resource) = handle { println!("{}: {:?}", i, resource); } @@ -301,8 +301,8 @@ fn cmd_alias(args: &[&str], config: &mut Config) -> Result<(), ExitCode> { let csi_option = Style::color("LightCyan"); let csi_title = Style::color("Yellow"); let csi_reset = Style::reset(); - println!("{}Usage:{} alias {} {1}", csi_title, csi_reset, csi_option); - return Err(ExitCode::Failure); + eprintln!("{}Usage:{} alias {} {1}", csi_title, csi_reset, csi_option); + return Err(ExitCode::UsageError); } config.aliases.insert(args[1].to_string(), args[2].to_string()); Ok(()) @@ -313,8 +313,8 @@ fn cmd_unalias(args: &[&str], config: &mut Config) -> Result<(), ExitCode> { let csi_option = Style::color("LightCyan"); let csi_title = Style::color("Yellow"); let csi_reset = Style::reset(); - println!("{}Usage:{} unalias {}{1}", csi_title, csi_reset, csi_option); - return Err(ExitCode::Failure); + eprintln!("{}Usage:{} unalias {}{1}", csi_title, csi_reset, csi_option); + return Err(ExitCode::UsageError); } if config.aliases.remove(&args[1].to_string()).is_none() { @@ -330,8 +330,8 @@ fn cmd_set(args: &[&str], config: &mut Config) -> Result<(), ExitCode> { let csi_option = Style::color("LightCyan"); let csi_title = Style::color("Yellow"); let csi_reset = Style::reset(); - println!("{}Usage:{} set {} {1}", csi_title, csi_reset, csi_option); - return Err(ExitCode::Failure); + eprintln!("{}Usage:{} set {} {1}", csi_title, csi_reset, csi_option); + return Err(ExitCode::UsageError); } config.env.insert(args[1].to_string(), args[2].to_string()); @@ -343,8 +343,8 @@ fn cmd_unset(args: &[&str], config: &mut Config) -> Result<(), ExitCode> { let csi_option = Style::color("LightCyan"); let csi_title = Style::color("Yellow"); let csi_reset = Style::reset(); - println!("{}Usage:{} unset {}{1}", csi_title, csi_reset, csi_option); - return Err(ExitCode::Failure); + eprintln!("{}Usage:{} unset {}{1}", csi_title, csi_reset, csi_option); + return Err(ExitCode::UsageError); } if config.env.remove(&args[1].to_string()).is_none() { @@ -380,7 +380,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> { let mut args: Vec<&str> = args.iter().map(String::as_str).collect(); // Redirections - let mut restore_file_handles = false; + let mut restore_handles = false; let mut n = args.len(); let mut i = 0; loop { @@ -417,7 +417,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> { continue; } - // Parse file handles + // Parse handles let mut num = String::new(); for c in args[i].chars() { match c { @@ -438,10 +438,10 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> { } if is_fat_arrow { // Redirections - restore_file_handles = true; + restore_handles = true; if !num.is_empty() { // if let Ok(right_handle) = num.parse() {} - println!("Redirecting to a file handle has not been implemented yet"); + println!("Redirecting to a handle has not been implemented yet"); return Err(ExitCode::Failure); } else { if i == n - 1 { @@ -538,7 +538,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> { // TODO: Remove this when redirections are done in spawned process - if restore_file_handles { + if restore_handles { for i in 0..3 { api::fs::reopen("/dev/console", i, false).ok(); } diff --git a/src/usr/socket.rs b/src/usr/socket.rs index 2b627142e..778df483e 100644 --- a/src/usr/socket.rs +++ b/src/usr/socket.rs @@ -1,39 +1,30 @@ use crate::{sys, usr, debug}; use crate::api::console::Style; -use crate::api::clock; use crate::api::io; +use crate::api::fs::IO; use crate::api::process::ExitCode; -use crate::api::random; use crate::api::syscall; +use crate::sys::fs::OpenFlag; +use crate::sys::net::SocketStatus; use alloc::format; -use alloc::string::String; use alloc::vec; use alloc::vec::Vec; +use bit_field::BitField; use core::str::{self, FromStr}; -use smoltcp::iface::SocketSet; -use smoltcp::socket::tcp; -use smoltcp::time::Instant; use smoltcp::wire::IpAddress; pub fn main(args: &[&str]) -> Result<(), ExitCode> { let mut listen = false; - let mut prompt = false; let mut verbose = false; let mut read_only = false; - let mut interval = 0.0; - let mut next_arg_is_interval = false; - let mut args: Vec<&str> = args.iter().filter_map(|arg| { + let args: Vec<&str> = args.iter().filter_map(|arg| { match *arg { "-l" | "--listen" => { listen = true; None } - "-p" | "--prompt" => { - prompt = true; - None - } - "-r" | "--read-only" => { + "-r" | "--read" => { read_only = true; None } @@ -41,45 +32,32 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { verbose = true; None } - "-i" | "--interval" => { - next_arg_is_interval = true; - None - } - _ if next_arg_is_interval => { - next_arg_is_interval = false; - if let Ok(i) = arg.parse() { - interval = i; - } - None - } _ => { Some(*arg) } } }).collect(); - if prompt { - println!("MOROS Socket v0.1.0\n"); - } - - let required_args_count = if listen { 2 } else { 3 }; - if args.len() == required_args_count - 1 { - if let Some(i) = args[1].find(':') { // Split and - let (host, path) = args[1].split_at(i); - args[1] = host; - args.push(&path[1..]); - } + if verbose { + println!("MOROS Socket v0.2.0\n"); } - if args.len() != required_args_count { + if args.len() != 2 { help(); return Err(ExitCode::UsageError); } - - let host = if listen { "0.0.0.0" } else { args[1] }; - let port: u16 = args[required_args_count - 1].parse().expect("Could not parse port"); - - let address = if host.ends_with(char::is_numeric) { + let (host, port) = match args[1].split_once(':') { + Some((h, p)) => (h, p), + None => ("0.0.0.0", args[1]), + }; + let port: u16 = match port.parse() { + Ok(n) => n, + Err(_) => { + eprint!("Could not parse port"); + return Err(ExitCode::UsageError); + } + }; + let addr = if host.ends_with(char::is_numeric) { IpAddress::from_str(host).expect("invalid address format") } else { match usr::host::resolve(host) { @@ -93,128 +71,82 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { } }; - #[derive(Debug)] - enum State { Connecting, Sending, Receiving } - let mut state = State::Connecting; + let socket_path = "/dev/net/tcp"; + let buf_len = if let Some(info) = syscall::info(socket_path) { + info.size() as usize + } else { + error!("Could not open '{}'", socket_path); + return Err(ExitCode::Failure); + }; - if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() { - let mut sockets = SocketSet::new(vec![]); - let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); - let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); - let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - let tcp_handle = sockets.add(tcp_socket); + let mut connected = false; + let stdin = 0; + let stdout = 1; + let flags = OpenFlag::Device as usize; + if let Some(handle) = syscall::open(socket_path, flags) { + if listen { + if syscall::listen(handle, port).is_err() { + error!("Could not listen to {}:{}", addr, port); + syscall::close(handle); + return Err(ExitCode::Failure); + } + if verbose { + debug!("Listening to {}:{}", addr, port); + } + } else { + if syscall::connect(handle, addr, port).is_ok() { + connected = true; + } else { + error!("Could not connect to {}:{}", addr, port); + syscall::close(handle); + return Err(ExitCode::Failure); + } + if verbose { + debug!("Connected to {}:{}", addr, port); + } + } loop { if sys::console::end_of_text() || sys::console::end_of_transmission() { - eprintln!(); - return Err(ExitCode::Failure); + println!(); + break; } - let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64); - iface.poll(time, device, &mut sockets); - let socket = sockets.get_mut::(tcp_handle); - let cx = iface.context(); - - if verbose { - debug!("*********************************"); - debug!("APP State: {:?}", state); - debug!("TCP State: {:?}", socket.state()); - debug!("is active: {}", socket.is_active()); - debug!("is open: {}", socket.is_open()); - debug!("can recv: {}", socket.can_recv()); - debug!("can send: {}", socket.can_send()); - debug!("may recv: {}", socket.may_recv()); - debug!("may send: {}", socket.may_send()); + if listen && !connected { + if syscall::accept(handle).is_ok() { + connected = true; + } else { + syscall::sleep(0.01); + continue; + } } - state = match state { - State::Connecting if !socket.is_active() => { - if listen { // Listen to a local port - if !socket.is_open() { - if verbose { - debug!("Listening to {}", port); - } - socket.listen(port).unwrap(); - } - } else { // Connect to a remote port - let local_port = 49152 + random::get_u16() % 16384; - if verbose { - debug!("Connecting to {}:{}", address, port); - } - if socket.connect(cx, (address, port), local_port).is_err() { - error!("Could not connect to {}:{}", address, port); - return Err(ExitCode::Failure); - } + let list = vec![(stdin, IO::Read), (handle, IO::Read)]; + if let Some((h, _)) = syscall::poll(&list) { + if h == stdin { + let line = io::stdin().read_line().replace("\n", "\r\n"); + syscall::write(handle, &line.as_bytes()); + } else { + let mut data = vec![0; buf_len]; + if let Some(bytes) = syscall::read(handle, &mut data) { + data.resize(bytes, 0); + syscall::write(stdout, &data); } - State::Receiving } - State::Sending if socket.can_recv() => { - if verbose { - debug!("Sending -> Receiving"); - } - State::Receiving - } - State::Sending if socket.can_send() && socket.may_recv() => { - if !read_only { - if verbose { - debug!("Sending ..."); - } - if prompt { - // Print prompt - print!("{}>{} ", Style::color("Cyan"), Style::reset()); + } else { + syscall::sleep(0.01); + if connected { + let mut data = vec![0; 1]; // 1 byte status read + match syscall::read(handle, &mut data) { + Some(1) if !data[0].get_bit(SocketStatus::MayRecv as usize) => { + break; // recv closed } - let line = io::stdin().read_line(); - if line.is_empty() { - socket.close(); - } else { - let line = line.replace("\n", "\r\n"); - socket.send_slice(line.as_ref()).expect("cannot send"); - } - } - State::Receiving - } - State::Receiving if socket.can_recv() => { - if verbose { - debug!("Receiving ..."); - } - socket.recv(|data| { - let contents = String::from_utf8_lossy(data); - print!("{}", contents.replace("\r\n", "\n")); - (data.len(), ()) - }).unwrap(); - State::Receiving - } - _ if socket.state() == tcp::State::SynSent || socket.state() == tcp::State::SynReceived => { - state - } - State::Receiving if !socket.may_recv() && !listen => { - if verbose { - debug!("Break from response"); + _ => continue, } - break; } - State::Receiving if socket.can_send() => { - if verbose { - debug!("Receiving -> Sending"); - } - State::Sending - } - _ if !socket.is_active() && !listen => { - if verbose { - debug!("Break from inactive"); - } - break; - } - _ => state - }; - - if interval > 0.0 { - syscall::sleep(interval); - } - if let Some(wait_duration) = iface.poll_delay(time, &sockets) { - syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0); } } + syscall::close(handle); Ok(()) } else { Err(ExitCode::Failure) @@ -225,12 +157,10 @@ fn help() { let csi_option = Style::color("LightCyan"); let csi_title = Style::color("Yellow"); let csi_reset = Style::reset(); - println!("{}Usage:{} socket {}[] {1}", csi_title, csi_reset, csi_option); + println!("{}Usage:{} socket {}[:]{1}", csi_title, csi_reset, csi_option); println!(); println!("{}Options:{}", csi_title, csi_reset); - println!(" {0}-l{1}, {0}--listen{1} Listen to a local port", csi_option, csi_reset); - println!(" {0}-v{1}, {0}--verbose{1} Increase verbosity", csi_option, csi_reset); - println!(" {0}-p{1}, {0}--prompt{1} Display prompt", csi_option, csi_reset); - println!(" {0}-r{1}, {0}--read-only{1} Read only connexion", csi_option, csi_reset); - println!(" {0}-i{1}, {0}--interval