mirror of https://github.com/vinc/moros.git
Add network syscalls (#512)
* Add connect syscall * Add tcp socket device * Add socket.connect to lisp * Rewrite tcp command to use the syscalls * Rewrite http command to use the syscalls * Use socket#recv_slice instead of socket#recv * Move connect to net module * Refactor connect * Refactor code * Add listen syscall * Add accept * Add close to FileIO * Fix write loop * Add poll syscall * Rewrite socket command to use poll * Update console polling * Rename file handle to handle * Remove prompt option from socket * Make poll blocking with timeout and cancel * Add one byte status read * Remove poll blocking * Fix error when connect is followed directly by close * Change tcp and socket command arguments * Add sleep while polling without events * Speed up connection failure detection * Add back some verbose output * Fix socket listen * Split net files * Add UDP sockets * Refactor host command code * Move shared socket code to module * Use recommended buffer size in socket file size * Remove debug output * Fix fallback socket size * Update html doc * Update network screenshot * Update lisp manual
This commit is contained in:
parent
eab33dc5c3
commit
b606c064c6
|
@ -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
|
||||
|
|
|
@ -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:<vinc@moros.cc>
|
||||
> RCPT TO:<alice@example.com>
|
||||
MAIL FROM:<vinc@moros.cc>
|
||||
250 Ok
|
||||
RCPT TO:<alice@example.com>
|
||||
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
|
||||
|
|
BIN
doc/network.png
BIN
doc/network.png
Binary file not shown.
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.3 KiB |
|
@ -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)`
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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<usize, ()>;
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, ()>;
|
||||
fn close(&mut self);
|
||||
fn poll(&mut self, event: IO) -> bool;
|
||||
}
|
||||
|
||||
pub fn dirname(pathname: &str) -> &str {
|
||||
|
|
|
@ -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<IpAddress, ()> {
|
||||
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};
|
||||
|
|
|
@ -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<usize, ()> {
|
||||
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<usize, ()> {
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<u8> {
|
||||
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<u8> for Device {
|
||||
|
@ -60,6 +68,8 @@ impl From<u8> 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<usize, ()> {
|
||||
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<usize, ()> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<usize, ()> {
|
||||
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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<Resource> {
|
|||
|
||||
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<String, ()> {
|
||||
|
|
|
@ -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<Option<(Interface, EthernetDevice)>> = 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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
pub mod rtl8139;
|
||||
pub mod pcnet;
|
|
@ -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<SocketSet<'static>> = 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);
|
||||
}
|
|
@ -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::<tcp::Socket>(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::<tcp::Socket>(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<IpAddress, ()> {
|
||||
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::<tcp::Socket>(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<usize, ()> {
|
||||
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::<tcp::Socket>(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<usize, ()> {
|
||||
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::<tcp::Socket>(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::<tcp::Socket>(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::<tcp::Socket>(self.handle);
|
||||
|
||||
match event {
|
||||
IO::Read => socket.can_recv(),
|
||||
IO::Write => socket.can_send(),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<IpEndpoint>,
|
||||
}
|
||||
|
||||
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::<udp::Socket>(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<IpAddress, ()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileIO for UdpSocket {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
|
||||
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::<udp::Socket>(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<usize, ()> {
|
||||
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::<udp::Socket>(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::<udp::Socket>(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::<udp::Socket>(self.handle);
|
||||
|
||||
match event {
|
||||
IO::Read => socket.can_recv(),
|
||||
IO::Write => socket.can_send(),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String>,
|
||||
dir: String,
|
||||
user: Option<String>,
|
||||
file_handles: [Option<Box<Resource>>; MAX_FILE_HANDLES],
|
||||
handles: [Option<Box<Resource>>; 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<usize, ()> {
|
||||
pub fn create_handle(file: Resource) -> Result<usize, ()> {
|
||||
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<Box<Resource>> {
|
||||
pub fn handle(handle: usize) -> Option<Box<Resource>> {
|
||||
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<Option<Box<Resource>>> {
|
||||
pub fn handles() -> Vec<Option<Box<Resource>>> {
|
||||
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 {
|
||||
|
|
|
@ -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<usize, ()> {
|
||||
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 {
|
||||
|
|
|
@ -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!();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<IpAddress, ()> {
|
||||
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(())
|
||||
}
|
||||
|
|
132
src/usr/host.rs
132
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<IpAddress> {
|
|||
}
|
||||
|
||||
pub fn resolve(name: &str) -> Result<IpAddress, ResponseCode> {
|
||||
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 addr = dns_address().unwrap_or(IpAddress::v4(8, 8, 8, 8));
|
||||
let port = 53;
|
||||
let query = Message::query(name, QueryType::A, QueryClass::IN);
|
||||
|
||||
let local_port = 49152 + random::get_u16() % 16384;
|
||||
let client = IpListenEndpoint::from(local_port);
|
||||
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 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 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)
|
||||
}
|
||||
|
||||
let timeout = 5.0;
|
||||
let started = clock::realtime();
|
||||
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::Socket>(udp_handle);
|
||||
|
||||
state = match state {
|
||||
State::Bind if !socket.is_open() => {
|
||||
socket.bind(client).unwrap();
|
||||
State::Query
|
||||
let mut data = vec![0; buf_len];
|
||||
if let Some(bytes) = syscall::read(handle, &mut data) {
|
||||
if bytes == 0 {
|
||||
break;
|
||||
}
|
||||
State::Query if socket.can_send() => {
|
||||
socket.send_slice(&query.datagram, server).expect("cannot send");
|
||||
State::Response
|
||||
}
|
||||
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)..];
|
||||
data.resize(bytes, 0);
|
||||
|
||||
Ok(IpAddress::from(Ipv4Address::from_bytes(rdata)))
|
||||
}
|
||||
rcode => {
|
||||
Err(rcode)
|
||||
}
|
||||
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) => {
|
||||
|
|
196
src/usr/http.rs
196
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::Socket>(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
|
||||
}
|
||||
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]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
(data.len(), ())
|
||||
}).unwrap();
|
||||
SessionState::Response
|
||||
}
|
||||
SessionState::Response if !socket.may_recv() => {
|
||||
let mut data = vec![0; buf_len];
|
||||
if let Some(n) = syscall::read(handle, &mut data) {
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
_ => session_state
|
||||
};
|
||||
|
||||
if let Some(wait_duration) = iface.poll_delay(time, &sockets) {
|
||||
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Could not read from {}:{}", addr, port);
|
||||
syscall::close(handle);
|
||||
return Err(ExitCode::Failure);
|
||||
}
|
||||
}
|
||||
syscall::close(handle);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExitCode::Failure)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -74,6 +74,9 @@ pub fn default_env() -> Rc<RefCell<Env>> {
|
|||
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();
|
||||
|
|
|
@ -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<Exp, Err> {
|
||||
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<Exp, Err> {
|
|||
_ => expected!("second argument to be a list")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lisp_socket_connect(args: &[Exp]) -> Result<Exp, Err> {
|
||||
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<Exp, Err> {
|
||||
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<Exp, Err> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/<host>/<path>");
|
||||
println!("{}Usage:{} read {}/net/<proto>/<host>[:<port>]/<path>{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];
|
||||
|
|
|
@ -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 {}<key> <val>{1}", csi_title, csi_reset, csi_option);
|
||||
return Err(ExitCode::Failure);
|
||||
eprintln!("{}Usage:{} alias {}<key> <val>{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 {}<key>{1}", csi_title, csi_reset, csi_option);
|
||||
return Err(ExitCode::Failure);
|
||||
eprintln!("{}Usage:{} unalias {}<key>{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 {}<key> <val>{1}", csi_title, csi_reset, csi_option);
|
||||
return Err(ExitCode::Failure);
|
||||
eprintln!("{}Usage:{} set {}<key> <val>{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 {}<key>{1}", csi_title, csi_reset, csi_option);
|
||||
return Err(ExitCode::Failure);
|
||||
eprintln!("{}Usage:{} unset {}<key>{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();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
if verbose {
|
||||
println!("MOROS Socket v0.2.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 <host> and <port>
|
||||
let (host, path) = args[1].split_at(i);
|
||||
args[1] = host;
|
||||
args.push(&path[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
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::Socket>(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);
|
||||
}
|
||||
}
|
||||
} 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
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
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());
|
||||
}
|
||||
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");
|
||||
}
|
||||
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 {}[<host>] <port>{1}", csi_title, csi_reset, csi_option);
|
||||
println!("{}Usage:{} socket {}[<host>:]<port>{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 <time>{1} Wait <time> between packets", csi_option, 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}-r{1}, {0}--read{1} Read only connexion", csi_option, csi_reset);
|
||||
}
|
||||
|
|
133
src/usr/tcp.rs
133
src/usr/tcp.rs
|
@ -1,22 +1,19 @@
|
|||
use crate::{sys, usr, debug};
|
||||
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::string::String;
|
||||
use alloc::format;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::str::{self, FromStr};
|
||||
use smoltcp::iface::SocketSet;
|
||||
use smoltcp::socket::tcp;
|
||||
use smoltcp::time::Instant;
|
||||
use core::str;
|
||||
use core::str::FromStr;
|
||||
use smoltcp::wire::IpAddress;
|
||||
|
||||
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
|
||||
let mut verbose = false;
|
||||
let mut args: Vec<&str> = args.iter().filter_map(|arg| {
|
||||
let args: Vec<&str> = args.iter().filter_map(|arg| {
|
||||
match *arg {
|
||||
"-v" | "--verbose" => {
|
||||
verbose = true;
|
||||
|
@ -28,25 +25,26 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
|
|||
}
|
||||
}).collect();
|
||||
|
||||
// Split <host> and <port>
|
||||
if args.len() == 2 {
|
||||
if let Some(i) = args[1].find(':') {
|
||||
let (host, path) = args[1].split_at(i);
|
||||
args[1] = host;
|
||||
args.push(&path[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() != 3 {
|
||||
if args.len() != 2 {
|
||||
help();
|
||||
return Err(ExitCode::UsageError);
|
||||
}
|
||||
|
||||
let host = &args[1];
|
||||
let port: u16 = args[2].parse().expect("Could not parse port");
|
||||
let request = "";
|
||||
|
||||
let address = if host.ends_with(char::is_numeric) {
|
||||
let (host, port) = match args[1].split_once(':') {
|
||||
Some((h, p)) => (h, p),
|
||||
None => {
|
||||
help();
|
||||
return Err(ExitCode::UsageError);
|
||||
}
|
||||
};
|
||||
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) {
|
||||
|
@ -54,77 +52,50 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
|
|||
ip_addr
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Could not resolve host: {:?}", e);
|
||||
error!("Could not resolve host {:?}", e);
|
||||
return Err(ExitCode::Failure);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum State { Connect, Request, Response }
|
||||
let mut state = State::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 timeout = 5.0;
|
||||
let started = clock::realtime();
|
||||
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);
|
||||
}
|
||||
if verbose {
|
||||
debug!("Connected to {}:{}", addr, port);
|
||||
}
|
||||
loop {
|
||||
if clock::realtime() - started > 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::Socket>(tcp_handle);
|
||||
let cx = iface.context();
|
||||
|
||||
state = match state {
|
||||
State::Connect if !socket.is_active() => {
|
||||
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);
|
||||
}
|
||||
State::Request
|
||||
}
|
||||
State::Request if socket.may_send() => {
|
||||
if !request.is_empty() {
|
||||
socket.send_slice(request.as_ref()).expect("cannot send");
|
||||
}
|
||||
State::Response
|
||||
}
|
||||
State::Response if socket.can_recv() => {
|
||||
socket.recv(|data| {
|
||||
let contents = String::from_utf8_lossy(data);
|
||||
for line in contents.lines() {
|
||||
println!("{}", line);
|
||||
}
|
||||
(data.len(), ())
|
||||
}).unwrap();
|
||||
State::Response
|
||||
}
|
||||
State::Response if !socket.may_recv() => {
|
||||
let mut data = vec![0; buf_len];
|
||||
if let Some(bytes) = syscall::read(handle, &mut data) {
|
||||
if bytes == 0 {
|
||||
break;
|
||||
}
|
||||
_ => state
|
||||
};
|
||||
|
||||
if let Some(wait_duration) = iface.poll_delay(time, &sockets) {
|
||||
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
|
||||
data.resize(bytes, 0);
|
||||
syscall::write(1, &data);
|
||||
} else {
|
||||
error!("Could not read from {}:{}", addr, port);
|
||||
syscall::close(handle);
|
||||
return Err(ExitCode::Failure);
|
||||
}
|
||||
}
|
||||
syscall::close(handle);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExitCode::Failure)
|
||||
|
@ -135,5 +106,5 @@ fn help() {
|
|||
let csi_option = Style::color("LightCyan");
|
||||
let csi_title = Style::color("Yellow");
|
||||
let csi_reset = Style::reset();
|
||||
println!("{}Usage:{} tcp {}<host> <port>{1}", csi_title, csi_reset, csi_option);
|
||||
println!("{}Usage:{} tcp {}<host>:<port>{1}", csi_title, csi_reset, csi_option);
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ of the Shell.</p>
|
|||
<li><code>let</code></li>
|
||||
<li><code>string.join</code> (aliased to <code>str.join</code>), <code>lines</code>, <code>words</code>, <code>chars</code></li>
|
||||
<li><code>regex.match?</code></li>
|
||||
<li><code>socket.connect</code>, <code>socket.listen</code>, <code>socket.accept</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>File Library</h3>
|
||||
|
@ -239,6 +240,7 @@ language and reading from the filesystem.</p>
|
|||
|
||||
<ul>
|
||||
<li>Add file, number, string, and regex namespaces</li>
|
||||
<li>Add socket functions</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -75,14 +75,14 @@ dns: 10.0.2.3
|
|||
<p>The <code>host</code> command performs DNS lookups:</p>
|
||||
|
||||
<pre><code>> host example.com
|
||||
example.com has address 93.184.216.34
|
||||
93.184.216.34
|
||||
</code></pre>
|
||||
|
||||
<h2>TCP</h2>
|
||||
|
||||
<p>The <code>tcp</code> command connects to TCP sockets:</p>
|
||||
|
||||
<pre><code>> tcp time.nist.gov 13 --verbose
|
||||
<pre><code>> 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) *
|
||||
|
@ -119,19 +119,17 @@ like the <code>netcat</code> command on Unix.</p>
|
|||
|
||||
<p>For example the request made with <code>tcp</code> above is equivalent to this:</p>
|
||||
|
||||
<pre><code>> socket time.nist.gov 13 --read-only
|
||||
<pre><code>> socket time.nist.gov:13 --read-only
|
||||
|
||||
59710 22-05-11 21:44:52 50 0 0 359.3 UTC(NIST) *
|
||||
</code></pre>
|
||||
|
||||
<p>And the request made with <code>http</code> is equivalent to that:</p>
|
||||
|
||||
<pre><code>> socket moros.cc 80 --prompt
|
||||
MOROS Socket v0.1.0
|
||||
<pre><code>> 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
|
||||
|
@ -156,31 +154,28 @@ Accept-Ranges: bytes
|
|||
|
||||
<p>Here's a connexion to a SMTP server to send a mail:</p>
|
||||
|
||||
<pre><code>> socket 10.0.2.2 2500 --prompt
|
||||
MOROS Socket v0.1.0
|
||||
|
||||
<pre><code>> 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:<vinc@moros.cc>
|
||||
> RCPT TO:<alice@example.com>
|
||||
MAIL FROM:<vinc@moros.cc>
|
||||
250 Ok
|
||||
RCPT TO:<alice@example.com>
|
||||
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
|
||||
</code></pre>
|
||||
|
||||
<p>Sending a file to a server:</p>
|
||||
|
||||
<pre><code>> socket 10.0.2.2 1234 <= /tmp/alice.txt
|
||||
<pre><code>> socket 10.0.2.2:1234 <= /tmp/alice.txt
|
||||
</code></pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -119,17 +119,17 @@ file while the standard error is kept:</p>
|
|||
</code></pre>
|
||||
|
||||
<p>The standard output is implied as the source of a redirection, but it is
|
||||
possible to explicitly redirect a file handle to another (TODO):</p>
|
||||
possible to explicitly redirect a handle to another (TODO):</p>
|
||||
|
||||
<pre><code>> time read foo.txt [1]=>[3]
|
||||
</code></pre>
|
||||
|
||||
<p>Or to redirect a file handle to a file:</p>
|
||||
<p>Or to redirect a handle to a file:</p>
|
||||
|
||||
<pre><code>> time read foo.txt [1]=> bar.txt
|
||||
</code></pre>
|
||||
|
||||
<p>Or to pipe a file handle to another command:</p>
|
||||
<p>Or to pipe a handle to another command:</p>
|
||||
|
||||
<pre><code>> time read foo.txt [1]-> write bar.txt
|
||||
</code></pre>
|
||||
|
@ -150,7 +150,7 @@ swapped and the standard input is implied (TODO):</p>
|
|||
<pre><code>> write <= req.txt => /net/http/moros.cc -> find --line href -> sort
|
||||
</code></pre>
|
||||
|
||||
<p>NOTE: The following file handles are available when a process is created:</p>
|
||||
<p>NOTE: The following handles are available when a process is created:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>stdin(0)</code></li>
|
||||
|
|
|
@ -66,5 +66,20 @@
|
|||
|
||||
<pre><code class="rust">pub fn sleep(seconds: f64)
|
||||
</code></pre>
|
||||
|
||||
<h2>CONNECT (0xC)</h2>
|
||||
|
||||
<pre><code class="rust">pub fn connect(handle, usize, addr: &str, port: u16) -> isize
|
||||
</code></pre>
|
||||
|
||||
<h2>LISTEN (0xD)</h2>
|
||||
|
||||
<pre><code class="rust">pub fn listen(handle, usize, port: u16) -> isize
|
||||
</code></pre>
|
||||
|
||||
<h2>ACCEPT (0xE)</h2>
|
||||
|
||||
<pre><code class="rust">pub fn accept(handle, usize, addr: &str) -> isize
|
||||
</code></pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue