mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-25 08:12:30 +00:00
🌈
This commit is contained in:
commit
cb5931d583
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
third_party
|
48
Cargo.toml
Normal file
48
Cargo.toml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"embassy-net",
|
||||||
|
"embassy-net-examples",
|
||||||
|
]
|
||||||
|
|
||||||
|
exclude = [
|
||||||
|
"third_party"
|
||||||
|
]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true
|
||||||
|
incremental = false
|
||||||
|
opt-level = 3
|
||||||
|
overflow-checks = true
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false
|
||||||
|
incremental = false
|
||||||
|
lto = "fat"
|
||||||
|
opt-level = 's'
|
||||||
|
overflow-checks = false
|
||||||
|
|
||||||
|
# do not optimize proc-macro crates = faster builds from scratch
|
||||||
|
[profile.dev.build-override]
|
||||||
|
codegen-units = 8
|
||||||
|
debug = false
|
||||||
|
debug-assertions = false
|
||||||
|
opt-level = 0
|
||||||
|
overflow-checks = false
|
||||||
|
|
||||||
|
[profile.release.build-override]
|
||||||
|
codegen-units = 8
|
||||||
|
debug = false
|
||||||
|
debug-assertions = false
|
||||||
|
opt-level = 0
|
||||||
|
overflow-checks = false
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
embassy = { git = "https://github.com/akiles/embassy" }
|
||||||
|
embassy-std = { git = "https://github.com/akiles/embassy" }
|
||||||
|
embassy-macros = { git = "https://github.com/akiles/embassy" }
|
||||||
|
smoltcp = { git = "https://github.com/akiles/smoltcp" }
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
25
LICENSE-MIT
Normal file
25
LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2020 Dario Nieuwenhuis
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# embassy-net
|
||||||
|
|
||||||
|
embassy-net contains an async network API based on smoltcp and embassy, designed
|
||||||
|
for embedded systems.
|
||||||
|
|
||||||
|
## Running the example
|
||||||
|
|
||||||
|
First, create the tap0 interface. You only need to do this once.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ip tuntap add name tap0 mode tap user $USER
|
||||||
|
sudo ip link set tap0 up
|
||||||
|
sudo ip addr add 192.168.69.100/24 dev tap0
|
||||||
|
sudo ip -6 addr add fe80::100/64 dev tap0
|
||||||
|
sudo ip -6 addr add fdaa::100/64 dev tap0
|
||||||
|
sudo ip -6 route add fe80::/64 dev tap0
|
||||||
|
sudo ip -6 route add fdaa::/64 dev tap0
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, run it
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --bin embassy-net-examples
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This work is licensed under either of
|
||||||
|
|
||||||
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
17
embassy-net-examples/Cargo.toml
Normal file
17
embassy-net-examples/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "embassy-net-examples"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
heapless = { version = "0.5.6", default-features = false }
|
||||||
|
embassy = { version = "0.1.0", features=["std", "log"] }
|
||||||
|
embassy-std = { version = "0.1.0" }
|
||||||
|
embassy-net = { version = "0.1.0", path = "../embassy-net", features=["std", "log"] }
|
||||||
|
env_logger = "0.8.2"
|
||||||
|
log = "0.4.11"
|
||||||
|
futures = "0.3.8"
|
||||||
|
libc = "0.2.81"
|
||||||
|
async-io = "1.3.1"
|
||||||
|
smoltcp = { version = "0.6.0", default-features = false }
|
79
embassy-net-examples/src/main.rs
Normal file
79
embassy-net-examples/src/main.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
use embassy::executor::{Spawner, task};
|
||||||
|
use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||||
|
use embassy::time::{Duration, Timer};
|
||||||
|
use embassy::util::Forever;
|
||||||
|
use embassy_net::*;
|
||||||
|
use embassy_std::Executor;
|
||||||
|
use heapless::Vec;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
mod tuntap;
|
||||||
|
|
||||||
|
use crate::tuntap::TunTapDevice;
|
||||||
|
|
||||||
|
static DEVICE: Forever<TunTapDevice> = Forever::new();
|
||||||
|
static CONFIG: Forever<StaticConfigurator> = Forever::new();
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
async fn net_task() {
|
||||||
|
embassy_net::run().await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
async fn main_task(spawner: Spawner) {
|
||||||
|
// Init network device
|
||||||
|
let device = TunTapDevice::new("tap0").unwrap();
|
||||||
|
|
||||||
|
// Static IP configuration
|
||||||
|
let config = StaticConfigurator::new(UpConfig {
|
||||||
|
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 1), 24),
|
||||||
|
dns_servers: Vec::new(),
|
||||||
|
gateway: Ipv4Address::new(192, 168, 69, 100),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init network stack
|
||||||
|
embassy_net::init(DEVICE.put(device), CONFIG.put(config));
|
||||||
|
|
||||||
|
// Launch network task
|
||||||
|
spawner.spawn(net_task()).unwrap();
|
||||||
|
|
||||||
|
// Then we can use it!
|
||||||
|
let mut rx_buffer = [0; 4096];
|
||||||
|
let mut tx_buffer = [0; 4096];
|
||||||
|
let mut socket = TcpSocket::new(&mut rx_buffer, &mut tx_buffer);
|
||||||
|
|
||||||
|
socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10)));
|
||||||
|
|
||||||
|
let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000);
|
||||||
|
info!("connecting to {:?}...", remote_endpoint);
|
||||||
|
let r = socket.connect(remote_endpoint).await;
|
||||||
|
if let Err(e) = r {
|
||||||
|
warn!("connect error: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
info!("connected!");
|
||||||
|
loop {
|
||||||
|
let r = socket.write_all(b"Hello!\n").await;
|
||||||
|
if let Err(e) = r {
|
||||||
|
warn!("write error: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static EXECUTOR: Forever<Executor> = Forever::new();
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Debug)
|
||||||
|
.filter_module("async_io", log::LevelFilter::Info)
|
||||||
|
.format_timestamp_nanos()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let executor = EXECUTOR.put(Executor::new());
|
||||||
|
executor.run(|spawner| {
|
||||||
|
spawner.spawn(main_task(spawner)).unwrap();
|
||||||
|
});
|
||||||
|
}
|
200
embassy-net-examples/src/tuntap.rs
Normal file
200
embassy-net-examples/src/tuntap.rs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
use async_io::Async;
|
||||||
|
use embassy::util::WakerRegistration;
|
||||||
|
use libc;
|
||||||
|
use smoltcp::wire::EthernetFrame;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
|
||||||
|
pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
|
||||||
|
pub const ETH_P_ALL: libc::c_short = 0x0003;
|
||||||
|
pub const TUNSETIFF: libc::c_ulong = 0x400454CA;
|
||||||
|
pub const IFF_TUN: libc::c_int = 0x0001;
|
||||||
|
pub const IFF_TAP: libc::c_int = 0x0002;
|
||||||
|
pub const IFF_NO_PI: libc::c_int = 0x1000;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ifreq {
|
||||||
|
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
|
||||||
|
ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ifreq_for(name: &str) -> ifreq {
|
||||||
|
let mut ifreq = ifreq {
|
||||||
|
ifr_name: [0; libc::IF_NAMESIZE],
|
||||||
|
ifr_data: 0,
|
||||||
|
};
|
||||||
|
for (i, byte) in name.as_bytes().iter().enumerate() {
|
||||||
|
ifreq.ifr_name[i] = *byte as libc::c_char
|
||||||
|
}
|
||||||
|
ifreq
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ifreq_ioctl(
|
||||||
|
lower: libc::c_int,
|
||||||
|
ifreq: &mut ifreq,
|
||||||
|
cmd: libc::c_ulong,
|
||||||
|
) -> io::Result<libc::c_int> {
|
||||||
|
unsafe {
|
||||||
|
let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq);
|
||||||
|
if res == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ifreq.ifr_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TunTap {
|
||||||
|
fd: libc::c_int,
|
||||||
|
ifreq: ifreq,
|
||||||
|
mtu: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for TunTap {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TunTap {
|
||||||
|
pub fn new(name: &str) -> io::Result<TunTap> {
|
||||||
|
unsafe {
|
||||||
|
let fd = libc::open(
|
||||||
|
"/dev/net/tun\0".as_ptr() as *const libc::c_char,
|
||||||
|
libc::O_RDWR | libc::O_NONBLOCK,
|
||||||
|
);
|
||||||
|
if fd == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ifreq = ifreq_for(name);
|
||||||
|
ifreq.ifr_data = IFF_TAP | IFF_NO_PI;
|
||||||
|
ifreq_ioctl(fd, &mut ifreq, TUNSETIFF)?;
|
||||||
|
|
||||||
|
let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
|
||||||
|
if socket == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let ip_mtu = ifreq_ioctl(socket, &mut ifreq, SIOCGIFMTU);
|
||||||
|
libc::close(socket);
|
||||||
|
let ip_mtu = ip_mtu? as usize;
|
||||||
|
|
||||||
|
// SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
|
||||||
|
// smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
|
||||||
|
let mtu = ip_mtu + EthernetFrame::<&[u8]>::header_len();
|
||||||
|
|
||||||
|
Ok(TunTap { fd, mtu, ifreq })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TunTap {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
libc::close(self.fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Read for TunTap {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let len = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
|
||||||
|
if len == -1 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(len as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for TunTap {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut libc::c_void, buf.len()) };
|
||||||
|
if len == -1 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(len as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TunTapDevice {
|
||||||
|
device: Async<TunTap>,
|
||||||
|
waker: WakerRegistration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TunTapDevice {
|
||||||
|
pub fn new(name: &str) -> io::Result<TunTapDevice> {
|
||||||
|
Ok(Self {
|
||||||
|
device: Async::new(TunTap::new(name)?)?,
|
||||||
|
waker: WakerRegistration::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use embassy_net::{LinkState, DeviceCapabilities, Packet, PacketBox, PacketBuf};
|
||||||
|
use core::task::Waker;
|
||||||
|
|
||||||
|
impl crate::Device for TunTapDevice {
|
||||||
|
fn is_transmit_ready(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transmit(&mut self, pkt: PacketBuf) {
|
||||||
|
// todo handle WouldBlock
|
||||||
|
match self.device.get_mut().write(&pkt) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||||
|
info!("transmit WouldBlock");
|
||||||
|
}
|
||||||
|
Err(e) => panic!("transmit error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&mut self) -> Option<PacketBuf> {
|
||||||
|
let mut pkt = PacketBox::new(Packet::new()).unwrap();
|
||||||
|
loop {
|
||||||
|
match self.device.get_mut().read(&mut pkt[..]) {
|
||||||
|
Ok(n) => {
|
||||||
|
return Some(pkt.slice(0..n));
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||||
|
let ready = if let Some(mut cx) = self.waker.context() {
|
||||||
|
let ready = self.device.poll_readable(&mut cx).is_ready();
|
||||||
|
ready
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if !ready {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => panic!("read error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_waker(&mut self, waker: &Waker) {
|
||||||
|
self.waker.register(waker)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capabilities(&mut self) -> DeviceCapabilities {
|
||||||
|
let mut caps = DeviceCapabilities::default();
|
||||||
|
caps.max_transmission_unit = self.device.get_ref().mtu;
|
||||||
|
caps
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_state(&mut self) -> LinkState {
|
||||||
|
LinkState::Up
|
||||||
|
}
|
||||||
|
}
|
46
embassy-net/Cargo.toml
Normal file
46
embassy-net/Cargo.toml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
[package]
|
||||||
|
name = "embassy-net"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
std = []
|
||||||
|
defmt-trace = []
|
||||||
|
defmt-debug = []
|
||||||
|
defmt-info = []
|
||||||
|
defmt-warn = []
|
||||||
|
defmt-error = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
defmt = { version = "0.1.3", optional = true }
|
||||||
|
log = { version = "0.4.11", optional = true }
|
||||||
|
|
||||||
|
embassy = { version = "0.1.0" }
|
||||||
|
|
||||||
|
managed = { version = "0.8.0", default-features = false, features = [ "map" ]}
|
||||||
|
heapless = { version = "0.5.6", default-features = false }
|
||||||
|
as-slice = { version = "0.1.4" }
|
||||||
|
generic-array = { version = "0.14.4", default-features = false }
|
||||||
|
stable_deref_trait = { version = "1.2.0", default-features = false }
|
||||||
|
futures = { version = "0.3.5", default-features = false, features = [ "async-await" ]}
|
||||||
|
|
||||||
|
[dependencies.smoltcp]
|
||||||
|
version = "0.6.0"
|
||||||
|
#git = "https://github.com/akiles/smoltcp"
|
||||||
|
#rev = "00952e2c5cdf5667a1dfb6142258055f58d3851c"
|
||||||
|
default-features = false
|
||||||
|
features = [
|
||||||
|
"medium-ethernet",
|
||||||
|
"medium-ip",
|
||||||
|
"proto-ipv4",
|
||||||
|
"proto-dhcpv4",
|
||||||
|
#"proto-igmp",
|
||||||
|
#"proto-ipv6",
|
||||||
|
#"socket-raw",
|
||||||
|
#"socket-icmp",
|
||||||
|
#"socket-udp",
|
||||||
|
"socket-tcp",
|
||||||
|
"async",
|
||||||
|
]
|
80
embassy-net/src/config/dhcp.rs
Normal file
80
embassy-net/src/config/dhcp.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use embassy::util::Forever;
|
||||||
|
use heapless::consts::*;
|
||||||
|
use heapless::Vec;
|
||||||
|
use smoltcp::dhcp::Dhcpv4Client;
|
||||||
|
use smoltcp::socket::{RawPacketMetadata, RawSocketBuffer};
|
||||||
|
use smoltcp::time::Instant;
|
||||||
|
use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{device::LinkState, fmt::*};
|
||||||
|
use crate::{Interface, SocketSet};
|
||||||
|
|
||||||
|
pub struct DhcpResources {
|
||||||
|
rx_buffer: [u8; 900],
|
||||||
|
tx_buffer: [u8; 600],
|
||||||
|
rx_meta: [RawPacketMetadata; 1],
|
||||||
|
tx_meta: [RawPacketMetadata; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DhcpConfigurator {
|
||||||
|
client: Option<Dhcpv4Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DhcpConfigurator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { client: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static DHCP_RESOURCES: Forever<DhcpResources> = Forever::new();
|
||||||
|
|
||||||
|
impl Configurator for DhcpConfigurator {
|
||||||
|
fn poll(
|
||||||
|
&mut self,
|
||||||
|
iface: &mut Interface,
|
||||||
|
sockets: &mut SocketSet,
|
||||||
|
timestamp: Instant,
|
||||||
|
) -> Option<Config> {
|
||||||
|
if self.client.is_none() {
|
||||||
|
let res = DHCP_RESOURCES.put(DhcpResources {
|
||||||
|
rx_buffer: [0; 900],
|
||||||
|
tx_buffer: [0; 600],
|
||||||
|
rx_meta: [RawPacketMetadata::EMPTY; 1],
|
||||||
|
tx_meta: [RawPacketMetadata::EMPTY; 1],
|
||||||
|
});
|
||||||
|
let rx_buffer = RawSocketBuffer::new(&mut res.rx_meta[..], &mut res.rx_buffer[..]);
|
||||||
|
let tx_buffer = RawSocketBuffer::new(&mut res.tx_meta[..], &mut res.tx_buffer[..]);
|
||||||
|
let dhcp = Dhcpv4Client::new(sockets, rx_buffer, tx_buffer, timestamp);
|
||||||
|
info!("created dhcp");
|
||||||
|
self.client = Some(dhcp)
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = self.client.as_mut().unwrap();
|
||||||
|
|
||||||
|
let link_up = iface.device_mut().device.link_state() == LinkState::Up;
|
||||||
|
if !link_up {
|
||||||
|
client.reset(timestamp);
|
||||||
|
return Some(Config::Down);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = client.poll(iface, sockets, timestamp).unwrap_or(None)?;
|
||||||
|
|
||||||
|
if config.address.is_none() {
|
||||||
|
return Some(Config::Down);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dns_servers = Vec::new();
|
||||||
|
for s in &config.dns_servers {
|
||||||
|
if let Some(addr) = s {
|
||||||
|
dns_servers.push(addr.clone()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(Config::Up(UpConfig {
|
||||||
|
address: config.address.unwrap(),
|
||||||
|
gateway: config.router.unwrap_or(Ipv4Address::UNSPECIFIED),
|
||||||
|
dns_servers,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
34
embassy-net/src/config/mod.rs
Normal file
34
embassy-net/src/config/mod.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use heapless::consts::*;
|
||||||
|
use heapless::Vec;
|
||||||
|
use smoltcp::time::Instant;
|
||||||
|
use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
|
||||||
|
|
||||||
|
use crate::fmt::*;
|
||||||
|
use crate::{Interface, SocketSet};
|
||||||
|
|
||||||
|
mod dhcp;
|
||||||
|
mod statik;
|
||||||
|
pub use dhcp::DhcpConfigurator;
|
||||||
|
pub use statik::StaticConfigurator;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Config {
|
||||||
|
Down,
|
||||||
|
Up(UpConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct UpConfig {
|
||||||
|
pub address: Ipv4Cidr,
|
||||||
|
pub gateway: Ipv4Address,
|
||||||
|
pub dns_servers: Vec<Ipv4Address, U3>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Configurator {
|
||||||
|
fn poll(
|
||||||
|
&mut self,
|
||||||
|
iface: &mut Interface,
|
||||||
|
sockets: &mut SocketSet,
|
||||||
|
timestamp: Instant,
|
||||||
|
) -> Option<Config>;
|
||||||
|
}
|
26
embassy-net/src/config/statik.rs
Normal file
26
embassy-net/src/config/statik.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use smoltcp::time::Instant;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::fmt::*;
|
||||||
|
use crate::{Interface, SocketSet};
|
||||||
|
|
||||||
|
pub struct StaticConfigurator {
|
||||||
|
config: UpConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticConfigurator {
|
||||||
|
pub fn new(config: UpConfig) -> Self {
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Configurator for StaticConfigurator {
|
||||||
|
fn poll(
|
||||||
|
&mut self,
|
||||||
|
_iface: &mut Interface,
|
||||||
|
_sockets: &mut SocketSet,
|
||||||
|
_timestamp: Instant,
|
||||||
|
) -> Option<Config> {
|
||||||
|
Some(Config::Up(self.config.clone()))
|
||||||
|
}
|
||||||
|
}
|
103
embassy-net/src/device.rs
Normal file
103
embassy-net/src/device.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use core::task::{Poll, Waker};
|
||||||
|
use smoltcp::phy::Device as SmolDevice;
|
||||||
|
use smoltcp::phy::DeviceCapabilities;
|
||||||
|
use smoltcp::time::Instant as SmolInstant;
|
||||||
|
use smoltcp::Result;
|
||||||
|
|
||||||
|
use crate::fmt::*;
|
||||||
|
use crate::{Packet, PacketBox, PacketBuf};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum LinkState {
|
||||||
|
Down,
|
||||||
|
Up,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Device {
|
||||||
|
fn is_transmit_ready(&mut self) -> bool;
|
||||||
|
fn transmit(&mut self, pkt: PacketBuf);
|
||||||
|
fn receive(&mut self) -> Option<PacketBuf>;
|
||||||
|
|
||||||
|
fn register_waker(&mut self, waker: &Waker);
|
||||||
|
fn capabilities(&mut self) -> DeviceCapabilities;
|
||||||
|
fn link_state(&mut self) -> LinkState;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceAdapter {
|
||||||
|
pub device: &'static mut dyn Device,
|
||||||
|
caps: DeviceCapabilities,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceAdapter {
|
||||||
|
pub(crate) fn new(device: &'static mut dyn Device) -> Self {
|
||||||
|
Self {
|
||||||
|
caps: device.capabilities(),
|
||||||
|
device,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SmolDevice<'a> for DeviceAdapter {
|
||||||
|
type RxToken = RxToken;
|
||||||
|
type TxToken = TxToken<'a>;
|
||||||
|
|
||||||
|
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||||
|
let rx_pkt = self.device.receive()?;
|
||||||
|
let tx_pkt = PacketBox::new(Packet::new()).unwrap(); // TODO: not sure about unwrap
|
||||||
|
let rx_token = RxToken { pkt: rx_pkt };
|
||||||
|
let tx_token = TxToken {
|
||||||
|
device: self.device,
|
||||||
|
pkt: tx_pkt,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((rx_token, tx_token))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a transmit token.
|
||||||
|
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||||
|
if !self.device.is_transmit_ready() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_pkt = PacketBox::new(Packet::new())?;
|
||||||
|
Some(TxToken {
|
||||||
|
device: self.device,
|
||||||
|
pkt: tx_pkt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a description of device capabilities.
|
||||||
|
fn capabilities(&self) -> DeviceCapabilities {
|
||||||
|
self.caps.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RxToken {
|
||||||
|
pkt: PacketBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl smoltcp::phy::RxToken for RxToken {
|
||||||
|
fn consume<R, F>(mut self, _timestamp: SmolInstant, f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||||
|
{
|
||||||
|
f(&mut self.pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TxToken<'a> {
|
||||||
|
device: &'a mut dyn Device,
|
||||||
|
pkt: PacketBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> smoltcp::phy::TxToken for TxToken<'a> {
|
||||||
|
fn consume<R, F>(mut self, _timestamp: SmolInstant, len: usize, f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||||
|
{
|
||||||
|
let mut buf = self.pkt.slice(0..len);
|
||||||
|
let r = f(&mut buf)?;
|
||||||
|
self.device.transmit(buf);
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
}
|
118
embassy-net/src/fmt.rs
Normal file
118
embassy-net/src/fmt.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#![macro_use]
|
||||||
|
|
||||||
|
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||||
|
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||||
|
|
||||||
|
pub use fmt::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
mod fmt {
|
||||||
|
pub use defmt::{
|
||||||
|
assert, assert_eq, assert_ne, debug, debug_assert, debug_assert_eq, debug_assert_ne, error,
|
||||||
|
info, panic, todo, trace, unreachable, unwrap, warn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
mod fmt {
|
||||||
|
pub use core::{
|
||||||
|
assert, assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, panic, todo,
|
||||||
|
unreachable,
|
||||||
|
};
|
||||||
|
pub use log::{debug, error, info, trace, warn};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "defmt", feature = "log")))]
|
||||||
|
mod fmt {
|
||||||
|
#![macro_use]
|
||||||
|
|
||||||
|
pub use core::{
|
||||||
|
assert, assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, panic, todo,
|
||||||
|
unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! trace {
|
||||||
|
($($msg:expr),+ $(,)?) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! debug {
|
||||||
|
($($msg:expr),+ $(,)?) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! info {
|
||||||
|
($($msg:expr),+ $(,)?) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! warn {
|
||||||
|
($($msg:expr),+ $(,)?) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! error {
|
||||||
|
($($msg:expr),+ $(,)?) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! unwrap {
|
||||||
|
($arg:expr) => {
|
||||||
|
match $crate::fmt::Try::into_result($arg) {
|
||||||
|
::core::result::Result::Ok(t) => t,
|
||||||
|
::core::result::Result::Err(e) => {
|
||||||
|
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||||
|
match $crate::fmt::Try::into_result($arg) {
|
||||||
|
::core::result::Result::Ok(t) => t,
|
||||||
|
::core::result::Result::Err(e) => {
|
||||||
|
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct NoneError;
|
||||||
|
|
||||||
|
pub trait Try {
|
||||||
|
type Ok;
|
||||||
|
type Error;
|
||||||
|
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Try for Option<T> {
|
||||||
|
type Ok = T;
|
||||||
|
type Error = NoneError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_result(self) -> Result<T, NoneError> {
|
||||||
|
self.ok_or(NoneError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Try for Result<T, E> {
|
||||||
|
type Ok = T;
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_result(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
31
embassy-net/src/lib.rs
Normal file
31
embassy-net/src/lib.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
#![feature(const_fn)]
|
||||||
|
#![feature(const_in_array_repeat_expressions)]
|
||||||
|
#![feature(const_generics)]
|
||||||
|
#![feature(const_evaluatable_checked)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
// This mod MUST go first, so that the others see its macros.
|
||||||
|
pub(crate) mod fmt;
|
||||||
|
|
||||||
|
mod pool; // TODO extract to embassy, or to own crate
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod device;
|
||||||
|
mod packet_pool;
|
||||||
|
mod stack;
|
||||||
|
mod tcp_socket;
|
||||||
|
|
||||||
|
pub use config::{Config, Configurator, DhcpConfigurator, StaticConfigurator, UpConfig};
|
||||||
|
pub use device::{Device, LinkState};
|
||||||
|
pub use packet_pool::{Packet, PacketBox, PacketBuf};
|
||||||
|
pub use stack::{init, is_init, run};
|
||||||
|
pub use tcp_socket::TcpSocket;
|
||||||
|
|
||||||
|
// smoltcp reexports
|
||||||
|
pub use smoltcp::phy::{DeviceCapabilities, Medium};
|
||||||
|
pub use smoltcp::time::Duration as SmolDuration;
|
||||||
|
pub use smoltcp::time::Instant as SmolInstant;
|
||||||
|
pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
|
||||||
|
pub type Interface = smoltcp::iface::Interface<'static, device::DeviceAdapter>;
|
||||||
|
pub type SocketSet = smoltcp::socket::SocketSet<'static>;
|
88
embassy-net/src/packet_pool.rs
Normal file
88
embassy-net/src/packet_pool.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
use as_slice::{AsMutSlice, AsSlice};
|
||||||
|
use core::ops::{Deref, DerefMut, Range};
|
||||||
|
|
||||||
|
use super::pool::{BitPool, Box, StaticPool};
|
||||||
|
|
||||||
|
pub const MTU: usize = 1514;
|
||||||
|
pub const PACKET_POOL_SIZE: usize = 4;
|
||||||
|
|
||||||
|
pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]);
|
||||||
|
pub type PacketBox = Box<PacketPool>;
|
||||||
|
|
||||||
|
pub struct Packet(pub [u8; MTU]);
|
||||||
|
|
||||||
|
impl Packet {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self([0; MTU])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Box<PacketPool> {
|
||||||
|
pub fn slice(self, range: Range<usize>) -> PacketBuf {
|
||||||
|
PacketBuf {
|
||||||
|
packet: self,
|
||||||
|
range,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsSlice for Packet {
|
||||||
|
type Element = u8;
|
||||||
|
|
||||||
|
fn as_slice(&self) -> &[Self::Element] {
|
||||||
|
&self.deref()[..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMutSlice for Packet {
|
||||||
|
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
|
||||||
|
&mut self.deref_mut()[..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Packet {
|
||||||
|
type Target = [u8; MTU];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[u8; MTU] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Packet {
|
||||||
|
fn deref_mut(&mut self) -> &mut [u8; MTU] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PacketBuf {
|
||||||
|
packet: PacketBox,
|
||||||
|
range: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsSlice for PacketBuf {
|
||||||
|
type Element = u8;
|
||||||
|
|
||||||
|
fn as_slice(&self) -> &[Self::Element] {
|
||||||
|
&self.packet[self.range.clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMutSlice for PacketBuf {
|
||||||
|
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
|
||||||
|
&mut self.packet[self.range.clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for PacketBuf {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[u8] {
|
||||||
|
&self.packet[self.range.clone()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for PacketBuf {
|
||||||
|
fn deref_mut(&mut self) -> &mut [u8] {
|
||||||
|
&mut self.packet[self.range.clone()]
|
||||||
|
}
|
||||||
|
}
|
245
embassy-net/src/pool.rs
Normal file
245
embassy-net/src/pool.rs
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#![macro_use]
|
||||||
|
|
||||||
|
use as_slice::{AsMutSlice, AsSlice};
|
||||||
|
use core::cmp;
|
||||||
|
use core::fmt;
|
||||||
|
use core::hash::{Hash, Hasher};
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
use core::ops::{Deref, DerefMut};
|
||||||
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
use crate::fmt::{assert, *};
|
||||||
|
|
||||||
|
struct AtomicBitset<const N: usize>
|
||||||
|
where
|
||||||
|
[AtomicU32; (N + 31) / 32]: Sized,
|
||||||
|
{
|
||||||
|
used: [AtomicU32; (N + 31) / 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> AtomicBitset<N>
|
||||||
|
where
|
||||||
|
[AtomicU32; (N + 31) / 32]: Sized,
|
||||||
|
{
|
||||||
|
const fn new() -> Self {
|
||||||
|
const Z: AtomicU32 = AtomicU32::new(0);
|
||||||
|
Self {
|
||||||
|
used: [Z; (N + 31) / 32],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc(&self) -> Option<usize> {
|
||||||
|
for (i, val) in self.used.iter().enumerate() {
|
||||||
|
let res = val.fetch_update(Ordering::AcqRel, Ordering::Acquire, |val| {
|
||||||
|
let n = val.trailing_ones() as usize + i * 32;
|
||||||
|
if n >= N {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(val | (1 << n))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Ok(val) = res {
|
||||||
|
let n = val.trailing_ones() as usize + i * 32;
|
||||||
|
return Some(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn free(&self, i: usize) {
|
||||||
|
assert!(i < N);
|
||||||
|
self.used[i / 32].fetch_and(!(1 << ((i % 32) as u32)), Ordering::AcqRel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Pool<T> {
|
||||||
|
fn alloc(&self) -> Option<*mut T>;
|
||||||
|
unsafe fn free(&self, p: *mut T);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BitPool<T, const N: usize>
|
||||||
|
where
|
||||||
|
[AtomicU32; (N + 31) / 32]: Sized,
|
||||||
|
{
|
||||||
|
used: AtomicBitset<N>,
|
||||||
|
data: MaybeUninit<[T; N]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> BitPool<T, N>
|
||||||
|
where
|
||||||
|
[AtomicU32; (N + 31) / 32]: Sized,
|
||||||
|
{
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
used: AtomicBitset::new(),
|
||||||
|
data: MaybeUninit::uninit(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Pool<T> for BitPool<T, N>
|
||||||
|
where
|
||||||
|
[AtomicU32; (N + 31) / 32]: Sized,
|
||||||
|
{
|
||||||
|
fn alloc(&self) -> Option<*mut T> {
|
||||||
|
let n = self.used.alloc()?;
|
||||||
|
let origin = self.data.as_ptr() as *mut T;
|
||||||
|
Some(unsafe { origin.add(n) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// safety: p must be a pointer obtained from self.alloc that hasn't been freed yet.
|
||||||
|
unsafe fn free(&self, p: *mut T) {
|
||||||
|
let origin = self.data.as_ptr() as *mut T;
|
||||||
|
let n = p.offset_from(origin);
|
||||||
|
assert!(n >= 0);
|
||||||
|
assert!((n as usize) < N);
|
||||||
|
self.used.free(n as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait StaticPool: 'static {
|
||||||
|
type Item: 'static;
|
||||||
|
type Pool: Pool<Self::Item>;
|
||||||
|
fn get() -> &'static Self::Pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Box<P: StaticPool> {
|
||||||
|
ptr: *mut P::Item,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> Box<P> {
|
||||||
|
pub fn new(item: P::Item) -> Option<Self> {
|
||||||
|
let p = match P::get().alloc() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
warn!("alloc failed!");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//trace!("allocated {:u32}", p as u32);
|
||||||
|
unsafe { p.write(item) };
|
||||||
|
Some(Self { ptr: p })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> Drop for Box<P> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
//trace!("dropping {:u32}", self.ptr as u32);
|
||||||
|
self.ptr.drop_in_place();
|
||||||
|
P::get().free(self.ptr);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<P: StaticPool> Send for Box<P> where P::Item: Send {}
|
||||||
|
|
||||||
|
unsafe impl<P: StaticPool> Sync for Box<P> where P::Item: Sync {}
|
||||||
|
|
||||||
|
unsafe impl<P: StaticPool> stable_deref_trait::StableDeref for Box<P> {}
|
||||||
|
|
||||||
|
impl<P: StaticPool> AsSlice for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: AsSlice,
|
||||||
|
{
|
||||||
|
type Element = <P::Item as AsSlice>::Element;
|
||||||
|
|
||||||
|
fn as_slice(&self) -> &[Self::Element] {
|
||||||
|
self.deref().as_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> AsMutSlice for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: AsMutSlice,
|
||||||
|
{
|
||||||
|
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
|
||||||
|
self.deref_mut().as_mut_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> Deref for Box<P> {
|
||||||
|
type Target = P::Item;
|
||||||
|
|
||||||
|
fn deref(&self) -> &P::Item {
|
||||||
|
unsafe { &*self.ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> DerefMut for Box<P> {
|
||||||
|
fn deref_mut(&mut self) -> &mut P::Item {
|
||||||
|
unsafe { &mut *self.ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> fmt::Debug for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
<P::Item as fmt::Debug>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> fmt::Display for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: fmt::Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
<P::Item as fmt::Display>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> PartialEq for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: PartialEq,
|
||||||
|
{
|
||||||
|
fn eq(&self, rhs: &Box<P>) -> bool {
|
||||||
|
<P::Item as PartialEq>::eq(self, rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> Eq for Box<P> where P::Item: Eq {}
|
||||||
|
|
||||||
|
impl<P: StaticPool> PartialOrd for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: PartialOrd,
|
||||||
|
{
|
||||||
|
fn partial_cmp(&self, rhs: &Box<P>) -> Option<cmp::Ordering> {
|
||||||
|
<P::Item as PartialOrd>::partial_cmp(self, rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> Ord for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: Ord,
|
||||||
|
{
|
||||||
|
fn cmp(&self, rhs: &Box<P>) -> cmp::Ordering {
|
||||||
|
<P::Item as Ord>::cmp(self, rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: StaticPool> Hash for Box<P>
|
||||||
|
where
|
||||||
|
P::Item: Hash,
|
||||||
|
{
|
||||||
|
fn hash<H>(&self, state: &mut H)
|
||||||
|
where
|
||||||
|
H: Hasher,
|
||||||
|
{
|
||||||
|
<P::Item as Hash>::hash(self, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pool {
|
||||||
|
($vis:vis $name:ident: [$ty:ty; $size:expr]) => {
|
||||||
|
$vis struct $name;
|
||||||
|
impl StaticPool for $name {
|
||||||
|
type Item = $ty;
|
||||||
|
type Pool = BitPool<$ty, $size>;
|
||||||
|
fn get() -> &'static Self::Pool {
|
||||||
|
static POOL: BitPool<$ty, $size> = BitPool::new();
|
||||||
|
&POOL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
212
embassy-net/src/stack.rs
Normal file
212
embassy-net/src/stack.rs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
use core::future::Future;
|
||||||
|
use core::task::Context;
|
||||||
|
use core::task::Poll;
|
||||||
|
use core::{cell::RefCell, future};
|
||||||
|
use embassy::time::{Instant, Timer};
|
||||||
|
use embassy::util::ThreadModeMutex;
|
||||||
|
use embassy::util::{Forever, WakerRegistration};
|
||||||
|
use futures::pin_mut;
|
||||||
|
use smoltcp::iface::{InterfaceBuilder, Neighbor, NeighborCache, Route, Routes};
|
||||||
|
use smoltcp::phy::Device as _;
|
||||||
|
use smoltcp::phy::Medium;
|
||||||
|
use smoltcp::socket::SocketSetItem;
|
||||||
|
use smoltcp::time::Instant as SmolInstant;
|
||||||
|
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
|
||||||
|
|
||||||
|
use crate::device::{Device, DeviceAdapter};
|
||||||
|
use crate::fmt::*;
|
||||||
|
use crate::{
|
||||||
|
config::{Config, Configurator},
|
||||||
|
device::LinkState,
|
||||||
|
};
|
||||||
|
use crate::{Interface, SocketSet};
|
||||||
|
|
||||||
|
const ADDRESSES_LEN: usize = 1;
|
||||||
|
const NEIGHBOR_CACHE_LEN: usize = 8;
|
||||||
|
const SOCKETS_LEN: usize = 2;
|
||||||
|
const LOCAL_PORT_MIN: u16 = 1025;
|
||||||
|
const LOCAL_PORT_MAX: u16 = 65535;
|
||||||
|
|
||||||
|
struct StackResources {
|
||||||
|
addresses: [IpCidr; ADDRESSES_LEN],
|
||||||
|
neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR_CACHE_LEN],
|
||||||
|
sockets: [Option<SocketSetItem<'static>>; SOCKETS_LEN],
|
||||||
|
routes: [Option<(IpCidr, Route)>; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
static STACK_RESOURCES: Forever<StackResources> = Forever::new();
|
||||||
|
static STACK: ThreadModeMutex<RefCell<Option<Stack>>> = ThreadModeMutex::new(RefCell::new(None));
|
||||||
|
|
||||||
|
pub(crate) struct Stack {
|
||||||
|
iface: Interface,
|
||||||
|
pub sockets: SocketSet,
|
||||||
|
link_up: bool,
|
||||||
|
next_local_port: u16,
|
||||||
|
configurator: &'static mut dyn Configurator,
|
||||||
|
waker: WakerRegistration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stack {
|
||||||
|
pub(crate) fn with<R>(f: impl FnOnce(&mut Stack) -> R) -> R {
|
||||||
|
let mut stack = STACK.borrow().borrow_mut();
|
||||||
|
let stack = stack.as_mut().unwrap();
|
||||||
|
f(stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_port(&mut self) -> u16 {
|
||||||
|
let res = self.next_local_port;
|
||||||
|
self.next_local_port = if res >= LOCAL_PORT_MAX {
|
||||||
|
LOCAL_PORT_MIN
|
||||||
|
} else {
|
||||||
|
res + 1
|
||||||
|
};
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wake(&mut self) {
|
||||||
|
self.waker.wake()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_configurator(&mut self, timestamp: SmolInstant) {
|
||||||
|
if let Some(config) = self
|
||||||
|
.configurator
|
||||||
|
.poll(&mut self.iface, &mut self.sockets, timestamp)
|
||||||
|
{
|
||||||
|
let medium = self.iface.device().capabilities().medium;
|
||||||
|
|
||||||
|
let (addr, gateway) = match config {
|
||||||
|
Config::Up(config) => (config.address.into(), Some(config.gateway)),
|
||||||
|
Config::Down => (IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32), None),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.iface.update_ip_addrs(|addrs| {
|
||||||
|
let curr_addr = &mut addrs[0];
|
||||||
|
if *curr_addr != addr {
|
||||||
|
info!("IPv4 address: {:?} -> {:?}", *curr_addr, addr);
|
||||||
|
*curr_addr = addr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if medium == Medium::Ethernet {
|
||||||
|
self.iface.routes_mut().update(|r| {
|
||||||
|
let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
|
||||||
|
let curr_gateway = r.get(&cidr).map(|r| r.via_router);
|
||||||
|
|
||||||
|
if curr_gateway != gateway.map(|a| a.into()) {
|
||||||
|
info!("IPv4 gateway: {:?} -> {:?}", curr_gateway, gateway);
|
||||||
|
if let Some(gateway) = gateway {
|
||||||
|
r.insert(cidr, Route::new_ipv4_gateway(gateway)).unwrap();
|
||||||
|
} else {
|
||||||
|
r.remove(&cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self, cx: &mut Context<'_>) {
|
||||||
|
self.iface.device_mut().device.register_waker(cx.waker());
|
||||||
|
self.waker.register(cx.waker());
|
||||||
|
|
||||||
|
let timestamp = instant_to_smoltcp(Instant::now());
|
||||||
|
if let Err(e) = self.iface.poll(&mut self.sockets, timestamp) {
|
||||||
|
// If poll() returns error, it may not be done yet, so poll again later.
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update link up
|
||||||
|
let old_link_up = self.link_up;
|
||||||
|
self.link_up = self.iface.device_mut().device.link_state() == LinkState::Up;
|
||||||
|
|
||||||
|
// Print when changed
|
||||||
|
if old_link_up != self.link_up {
|
||||||
|
if self.link_up {
|
||||||
|
info!("Link up!");
|
||||||
|
} else {
|
||||||
|
info!("Link down!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if old_link_up || self.link_up {
|
||||||
|
self.poll_configurator(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(poll_at) = self.iface.poll_at(&mut self.sockets, timestamp) {
|
||||||
|
let t = Timer::at(instant_from_smoltcp(poll_at));
|
||||||
|
pin_mut!(t);
|
||||||
|
if t.poll(cx).is_ready() {
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize embassy_net.
|
||||||
|
/// This function must be called from thread mode.
|
||||||
|
pub fn init(device: &'static mut dyn Device, configurator: &'static mut dyn Configurator) {
|
||||||
|
let res = STACK_RESOURCES.put(StackResources {
|
||||||
|
addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32)],
|
||||||
|
neighbor_cache: [None; NEIGHBOR_CACHE_LEN],
|
||||||
|
sockets: [None; SOCKETS_LEN],
|
||||||
|
routes: [None; 1],
|
||||||
|
});
|
||||||
|
|
||||||
|
let ethernet_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
|
||||||
|
|
||||||
|
let medium = device.capabilities().medium;
|
||||||
|
|
||||||
|
let mut b = InterfaceBuilder::new(DeviceAdapter::new(device));
|
||||||
|
b = b.ip_addrs(&mut res.addresses[..]);
|
||||||
|
|
||||||
|
if medium == Medium::Ethernet {
|
||||||
|
b = b.ethernet_addr(ethernet_addr);
|
||||||
|
b = b.neighbor_cache(NeighborCache::new(&mut res.neighbor_cache[..]));
|
||||||
|
b = b.routes(Routes::new(&mut res.routes[..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let iface = b.finalize();
|
||||||
|
|
||||||
|
let sockets = SocketSet::new(&mut res.sockets[..]);
|
||||||
|
|
||||||
|
let local_port = loop {
|
||||||
|
let mut res = [0u8; 2];
|
||||||
|
embassy::rand::rand(&mut res);
|
||||||
|
let port = u16::from_le_bytes(res);
|
||||||
|
if port >= LOCAL_PORT_MIN && port <= LOCAL_PORT_MAX {
|
||||||
|
break port;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stack = Stack {
|
||||||
|
iface,
|
||||||
|
sockets,
|
||||||
|
link_up: false,
|
||||||
|
configurator,
|
||||||
|
next_local_port: local_port,
|
||||||
|
waker: WakerRegistration::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*STACK.borrow().borrow_mut() = Some(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_init() -> bool {
|
||||||
|
STACK.borrow().borrow().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run() {
|
||||||
|
futures::future::poll_fn(|cx| {
|
||||||
|
Stack::with(|stack| stack.poll(cx));
|
||||||
|
Poll::<()>::Pending
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
|
||||||
|
SmolInstant::from_millis(instant.as_millis() as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
|
||||||
|
Instant::from_millis(instant.total_millis() as u64)
|
||||||
|
}
|
178
embassy-net/src/tcp_socket.rs
Normal file
178
embassy-net/src/tcp_socket.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
use core::mem;
|
||||||
|
use core::pin::Pin;
|
||||||
|
use core::task::{Context, Poll};
|
||||||
|
use embassy::io;
|
||||||
|
use embassy::io::{AsyncBufRead, AsyncWrite};
|
||||||
|
use smoltcp::socket::SocketHandle;
|
||||||
|
use smoltcp::socket::TcpSocket as SyncTcpSocket;
|
||||||
|
use smoltcp::socket::{TcpSocketBuffer, TcpState};
|
||||||
|
use smoltcp::time::Duration;
|
||||||
|
use smoltcp::wire::IpEndpoint;
|
||||||
|
use smoltcp::{Error, Result};
|
||||||
|
|
||||||
|
use super::stack::Stack;
|
||||||
|
use crate::fmt::*;
|
||||||
|
|
||||||
|
pub struct TcpSocket<'a> {
|
||||||
|
handle: SocketHandle,
|
||||||
|
ghost: PhantomData<&'a mut [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Unpin for TcpSocket<'a> {}
|
||||||
|
|
||||||
|
impl<'a> TcpSocket<'a> {
|
||||||
|
pub fn new(rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
|
||||||
|
let handle = Stack::with(|stack| {
|
||||||
|
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||||
|
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||||
|
stack.sockets.add(SyncTcpSocket::new(
|
||||||
|
TcpSocketBuffer::new(rx_buffer),
|
||||||
|
TcpSocketBuffer::new(tx_buffer),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
ghost: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect<T>(&mut self, remote_endpoint: T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: Into<IpEndpoint>,
|
||||||
|
{
|
||||||
|
let local_port = Stack::with(|stack| stack.get_local_port());
|
||||||
|
self.with(|s| s.connect(remote_endpoint, local_port))?;
|
||||||
|
|
||||||
|
futures::future::poll_fn(|cx| {
|
||||||
|
self.with(|s| match s.state() {
|
||||||
|
TcpState::Closed | TcpState::TimeWait => Poll::Ready(Err(Error::Unaddressable)),
|
||||||
|
TcpState::Listen => Poll::Ready(Err(Error::Illegal)),
|
||||||
|
TcpState::SynSent | TcpState::SynReceived => {
|
||||||
|
s.register_send_waker(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
_ => Poll::Ready(Ok(())),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_timeout(&mut self, duration: Option<Duration>) {
|
||||||
|
self.with(|s| s.set_timeout(duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
|
||||||
|
self.with(|s| s.set_keep_alive(interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
|
||||||
|
self.with(|s| s.set_hop_limit(hop_limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_endpoint(&self) -> IpEndpoint {
|
||||||
|
self.with(|s| s.local_endpoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_endpoint(&self) -> IpEndpoint {
|
||||||
|
self.with(|s| s.remote_endpoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> TcpState {
|
||||||
|
self.with(|s| s.state())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&mut self) {
|
||||||
|
self.with(|s| s.close())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abort(&mut self) {
|
||||||
|
self.with(|s| s.abort())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn may_send(&self) -> bool {
|
||||||
|
self.with(|s| s.may_send())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn may_recv(&self) -> bool {
|
||||||
|
self.with(|s| s.may_recv())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with<R>(&self, f: impl FnOnce(&mut SyncTcpSocket) -> R) -> R {
|
||||||
|
Stack::with(|stack| {
|
||||||
|
let res = {
|
||||||
|
let mut s = stack.sockets.get::<SyncTcpSocket>(self.handle);
|
||||||
|
f(&mut *s)
|
||||||
|
};
|
||||||
|
stack.wake();
|
||||||
|
res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_ioerr(e: Error) -> io::Error {
|
||||||
|
warn!("smoltcp err: {:?}", e);
|
||||||
|
// todo
|
||||||
|
io::Error::Other
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for TcpSocket<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
Stack::with(|stack| {
|
||||||
|
stack.sockets.remove(self.handle);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AsyncBufRead for TcpSocket<'a> {
|
||||||
|
fn poll_fill_buf<'z>(
|
||||||
|
self: Pin<&'z mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<io::Result<&'z [u8]>> {
|
||||||
|
self.with(|socket| match socket.peek(1 << 30) {
|
||||||
|
// No data ready
|
||||||
|
Ok(buf) if buf.len() == 0 => {
|
||||||
|
socket.register_recv_waker(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
// Data ready!
|
||||||
|
Ok(buf) => {
|
||||||
|
// Safety:
|
||||||
|
// - User can't touch the inner TcpSocket directly at all.
|
||||||
|
// - The socket itself won't touch these bytes until consume() is called, which
|
||||||
|
// requires the user to release this borrow.
|
||||||
|
let buf: &'z [u8] = unsafe { core::mem::transmute(&*buf) };
|
||||||
|
Poll::Ready(Ok(buf))
|
||||||
|
}
|
||||||
|
// EOF
|
||||||
|
Err(Error::Finished) => Poll::Ready(Ok(&[][..])),
|
||||||
|
// Error
|
||||||
|
Err(e) => Poll::Ready(Err(to_ioerr(e))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||||
|
self.with(|s| s.recv(|_| (amt, ()))).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AsyncWrite for TcpSocket<'a> {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
self.with(|s| match s.send_slice(buf) {
|
||||||
|
// Not ready to send (no space in the tx buffer)
|
||||||
|
Ok(0) => {
|
||||||
|
s.register_send_waker(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
// Some data sent
|
||||||
|
Ok(n) => Poll::Ready(Ok(n)),
|
||||||
|
// Error
|
||||||
|
Err(e) => Poll::Ready(Err(to_ioerr(e))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
25
test-build.sh
Executable file
25
test-build.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
# embassy std
|
||||||
|
(cd embassy; cargo build --features log,std)
|
||||||
|
|
||||||
|
# embassy embedded
|
||||||
|
(cd embassy; cargo build --target thumbv7em-none-eabi)
|
||||||
|
(cd embassy; cargo build --target thumbv7em-none-eabi --features log)
|
||||||
|
(cd embassy; cargo build --target thumbv7em-none-eabi --features defmt)
|
||||||
|
|
||||||
|
# embassy-nrf
|
||||||
|
|
||||||
|
(cd embassy-nrf-examples; cargo build --target thumbv7em-none-eabi --bins)
|
||||||
|
|
||||||
|
(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52810)
|
||||||
|
#(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52811) # nrf52811-hal doesn't exist yet
|
||||||
|
(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52832)
|
||||||
|
(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52833)
|
||||||
|
(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52840)
|
||||||
|
|
||||||
|
(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52840,log)
|
||||||
|
(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52840,defmt)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user