foramt services/dns

This commit is contained in:
bunnie 2024-01-14 22:08:14 +08:00
parent 8ef2d2d8f2
commit e096384f66
6 changed files with 332 additions and 320 deletions

View file

@ -19,7 +19,8 @@ pub(crate) enum Opcode {
/// used internally to update the TTL field, and eventually expire the cache (unless cache is frozen)
UpdateTtl = 2,
/// issuing this opcode causes all future attempts to change the DNS server configs to be ignored. This also freezes the cache.
/// issuing this opcode causes all future attempts to change the DNS server configs to be ignored. This
/// also freezes the cache.
FreezeConfig = 3,
/// this allows automatic updates to the DNS server configs based on DHCP. This is the default state.
@ -52,14 +53,7 @@ pub(crate) enum Opcode {
}
#[derive(
Debug,
num_derive::FromPrimitive,
num_derive::ToPrimitive,
Archive,
Serialize,
Deserialize,
Copy,
Clone,
Debug, num_derive::FromPrimitive, num_derive::ToPrimitive, Archive, Serialize, Deserialize, Copy, Clone,
)]
#[repr(u16)]
pub enum DnsResponseCode {

View file

@ -1,39 +1,37 @@
use net::NetIpAddr;
use std::net::ToSocketAddrs;
use net::NetIpAddr;
use crate::DnsResponseCode;
#[derive(Debug)]
pub struct Dns {
}
pub struct Dns {}
impl Dns {
pub fn new(_xns: &xous_names::XousNames) -> Result<Self, xous::Error> {
Ok(Dns {
})
}
pub fn new(_xns: &xous_names::XousNames) -> Result<Self, xous::Error> { Ok(Dns {}) }
/// Checks first to see if the name could be just an IPv4 or IPv6 in string form,
/// then tries to pass it to the DNS resolver.
pub fn lookup(&self, name: &str) -> Result<NetIpAddr, DnsResponseCode> {
log::debug!("looking up {}", name);
match (name, 80).to_socket_addrs() { // we throw away the port because we just want the IP address...
Ok(mut iter) => {
match iter.next() {
Some(addr) => {
log::debug!("{:?}", addr);
Ok(NetIpAddr::from(addr))
},
None => {
log::debug!("name error");
Err(DnsResponseCode::NameError)
}
match (name, 80).to_socket_addrs() {
// we throw away the port because we just want the IP address...
Ok(mut iter) => match iter.next() {
Some(addr) => {
log::debug!("{:?}", addr);
Ok(NetIpAddr::from(addr))
}
}
None => {
log::debug!("name error");
Err(DnsResponseCode::NameError)
}
},
Err(e) => {
log::debug!("format error: {:?}", e);
Err(DnsResponseCode::FormatError)
}
}
}
pub fn flush_cache(&self) -> Result<(), xous::Error> {
log::warn!("DNS cache flush not implemented in hosted mode!");
Ok(())

View file

@ -1,10 +1,10 @@
#![cfg_attr(target_os = "none", no_std)]
use xous::CID;
use xous_ipc::{Buffer, String};
use num_traits::ToPrimitive;
use std::net::IpAddr;
use net::NetIpAddr;
use std::net::IpAddr;
use num_traits::ToPrimitive;
use xous::CID;
use xous_ipc::{Buffer, String};
use crate::api::*;
@ -15,10 +15,10 @@ pub struct Dns {
impl Dns {
pub fn new(xns: &xous_names::XousNames) -> Result<Self, xous::Error> {
REFCOUNT.fetch_add(1, Ordering::Relaxed);
let conn = xns.request_connection_blocking(crate::api::SERVER_NAME_DNS).expect("Can't connect to Dns server");
Ok(Dns {
conn
})
let conn = xns
.request_connection_blocking(crate::api::SERVER_NAME_DNS)
.expect("Can't connect to Dns server");
Ok(Dns { conn })
}
/// Checks first to see if the name could be just an IPv4 or IPv6 in string form,
@ -31,19 +31,17 @@ impl Dns {
let mut buf = Buffer::into_buf(alloc_name).or(Err(DnsResponseCode::UnknownError))?;
buf.lend_mut(self.conn, Opcode::Lookup.to_u32().unwrap())
.or(Err(DnsResponseCode::UnknownError))?;
let response = buf.to_original::<DnsResponse,_>().or(Err(DnsResponseCode::UnknownError))?;
if let Some(addr) = response.addr {
Ok(addr)
} else {
Err(response.code)
}
let response = buf.to_original::<DnsResponse, _>().or(Err(DnsResponseCode::UnknownError))?;
if let Some(addr) = response.addr { Ok(addr) } else { Err(response.code) }
}
}
pub fn flush_cache(&self) -> Result<(), xous::Error> {
xous::send_message(
self.conn,
xous::Message::new_scalar(Opcode::Flush.to_usize().unwrap(), 0, 0, 0, 0)
).map(|_| ())
xous::Message::new_scalar(Opcode::Flush.to_usize().unwrap(), 0, 0, 0, 0),
)
.map(|_| ())
}
}
@ -51,13 +49,17 @@ use core::sync::atomic::{AtomicU32, Ordering};
static REFCOUNT: AtomicU32 = AtomicU32::new(0);
impl Drop for Dns {
fn drop(&mut self) {
// the connection to the server side must be reference counted, so that multiple instances of this object within
// a single process do not end up de-allocating the CID on other threads before they go out of scope.
// Note to future me: you want this. Don't get rid of it because you think, "nah, nobody will ever make more than one copy of this object".
// the connection to the server side must be reference counted, so that multiple instances of this
// object within a single process do not end up de-allocating the CID on other threads before
// they go out of scope. Note to future me: you want this. Don't get rid of it because you
// think, "nah, nobody will ever make more than one copy of this object".
if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 {
unsafe{xous::disconnect(self.conn).unwrap();}
unsafe {
xous::disconnect(self.conn).unwrap();
}
}
// if there was object-specific state (such as a one-time use server for async callbacks, specific to the object instance),
// de-allocate those items here. They don't need a reference count because they are object-specific
// if there was object-specific state (such as a one-time use server for async callbacks, specific to
// the object instance), de-allocate those items here. They don't need a reference count
// because they are object-specific
}
}
}

View file

@ -2,9 +2,9 @@ pub mod api;
#[allow(unused_imports)]
pub use api::*;
#[cfg(any(feature="precursor", feature="renode"))]
#[cfg(any(feature = "precursor", feature = "renode"))]
mod hw;
#[cfg(any(feature="precursor", feature="renode"))]
#[cfg(any(feature = "precursor", feature = "renode"))]
pub use hw::*;
#[cfg(not(target_os = "xous"))]

View file

@ -3,18 +3,17 @@
mod api;
mod time; // why is this here? because it's the only place it'll fit. :-/
use api::*;
use net::NetIpAddr;
use num_traits::*;
use xous::msg_scalar_unpack;
use std::collections::HashMap;
use std::convert::TryInto;
use std::io::ErrorKind;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
use std::time::Duration;
use std::thread;
use std::time::Duration;
use api::*;
use net::NetIpAddr;
use num_traits::*;
use xous::msg_scalar_unpack;
use xous_ipc::{Buffer, String};
// KISS DNS
@ -47,11 +46,7 @@ struct Message {
const FLAG_RD: u16 = 0x0100; // Recursion desired
impl Message {
pub fn from(datagram: &[u8]) -> Self {
Self {
datagram: Vec::from(datagram),
}
}
pub fn from(datagram: &[u8]) -> Self { Self { datagram: Vec::from(datagram) } }
pub fn query(qname: &str, qtype: QueryType, qclass: QueryClass, id: u16) -> Self {
let mut datagram = Vec::new();
@ -85,21 +80,11 @@ impl Message {
Self { datagram }
}
pub fn id(&self) -> u16 {
u16::from_be_bytes(self.datagram[0..2].try_into().unwrap())
}
pub fn id(&self) -> u16 { u16::from_be_bytes(self.datagram[0..2].try_into().unwrap()) }
pub fn header(&self) -> u16 {
u16::from_be_bytes(self.datagram[2..4].try_into().unwrap())
}
pub fn header(&self) -> u16 { u16::from_be_bytes(self.datagram[2..4].try_into().unwrap()) }
pub fn is_response(&self) -> bool {
if (self.header() & (1 << 15)) == 0 {
false
} else {
true
}
}
pub fn is_response(&self) -> bool { if (self.header() & (1 << 15)) == 0 { false } else { true } }
fn fast_foward_name(&self, start: usize) -> Result<usize, DnsResponseCode> {
use DnsResponseCode::FormatError;
@ -156,9 +141,7 @@ impl Message {
// pointer
index += 1;
if self.datagram[index] != 0xc {
log::error!(
"Found aname pointer, but value does not conform to length of aname header"
);
log::error!("Found aname pointer, but value does not conform to length of aname header");
return Err(FormatError);
}
index += 1;
@ -195,10 +178,10 @@ impl Message {
log::error!("Got a 4-byte address, but ATYPE != A (1)");
return Err(FormatError);
}
// this copy happens because I can't figure out how to get Ipv4Addr::from() to realize it's casting from a [u8;4]
// this copy happens because I can't figure out how to get Ipv4Addr::from() to realize
// it's casting from a [u8;4]
let mut rdata: [u8; 4] = [0; 4];
for (&src, dst) in self.datagram[index..index + 4].iter().zip(rdata.iter_mut())
{
for (&src, dst) in self.datagram[index..index + 4].iter().zip(rdata.iter_mut()) {
*dst = src;
}
let addr = IpAddr::V4(Ipv4Addr::from(rdata));
@ -211,12 +194,10 @@ impl Message {
log::error!("Got a 16-byte address, but ATYPE != AAAA (28)");
return Err(FormatError);
}
// this copy happens because I can't figure out how to get Ipv6Addr::from() to realize it's casting from a [u8;4]
// this copy happens because I can't figure out how to get Ipv6Addr::from() to realize
// it's casting from a [u8;4]
let mut rdata: [u8; 16] = [0; 16];
for (&src, dst) in self.datagram[index..index + 16]
.iter()
.zip(rdata.iter_mut())
{
for (&src, dst) in self.datagram[index..index + 16].iter().zip(rdata.iter_mut()) {
*dst = src;
}
let addr = IpAddr::V6(Ipv6Addr::from(rdata));
@ -317,15 +298,13 @@ impl Resolver {
pub fn new(xns: &xous_names::XousNames) -> Resolver {
let trng = trng::Trng::new(&xns).unwrap();
let local_port = (49152 + trng.get_u32().unwrap() % 16384) as u16;
let socket = UdpSocket::bind(
format!("0.0.0.0:{}", local_port),
)
.expect("couldn't create socket for DNS resolver");
let socket = UdpSocket::bind(format!("0.0.0.0:{}", local_port))
.expect("couldn't create socket for DNS resolver");
let timeout = Duration::from_millis(10_000); // 10 seconds for DNS to resolve by default
socket.set_read_timeout(Some(timeout)).unwrap();
socket.set_nonblocking(false).unwrap(); // we want this to block.
// we /could/ do a non-blocking DNS resolver, but...what would you do in the meantime??
// blocking is probably what we actually want this time.
// we /could/ do a non-blocking DNS resolver, but...what would you do in the meantime??
// blocking is probably what we actually want this time.
Resolver {
mgr: net::protocols::DnsServerManager::register(&xns)
@ -336,26 +315,23 @@ impl Resolver {
freeze: false,
}
}
pub fn add_server(&mut self, addr: IpAddr) {
self.mgr.add_server(addr);
}
pub fn remove_server(&mut self, addr: IpAddr) {
self.mgr.remove_server(addr);
}
pub fn clear_all_servers(&mut self) {
self.mgr.clear();
}
pub fn add_server(&mut self, addr: IpAddr) { self.mgr.add_server(addr); }
pub fn remove_server(&mut self, addr: IpAddr) { self.mgr.remove_server(addr); }
pub fn clear_all_servers(&mut self) { self.mgr.clear(); }
pub fn set_freeze_config(&mut self, freeze: bool) {
self.freeze = freeze;
self.mgr.set_freeze(freeze);
}
pub fn get_freeze(&self) -> bool {
self.freeze
}
pub fn get_freeze(&self) -> bool { self.freeze }
/// this allows us to re-use the TRNG object
pub fn trng_u32(&self) -> u32 {
self.trng.get_u32().unwrap()
}
pub fn trng_u32(&self) -> u32 { self.trng.get_u32().unwrap() }
pub fn resolve(&mut self, name: &str) -> Result<HashMap<IpAddr, u32>, DnsResponseCode> {
if let Some(dns_address) = self.mgr.get_random() {
let dns_port = 53;
@ -366,9 +342,7 @@ impl Resolver {
let qclass = QueryClass::IN;
let query = Message::query(qname, qtype, qclass, self.trng.get_u32().unwrap() as u16);
self.socket
.send_to(&query.datagram, &server)
.map_err(|_| DnsResponseCode::NetworkError)?;
self.socket.send_to(&query.datagram, &server).map_err(|_| DnsResponseCode::NetworkError)?;
match self.socket.recv(&mut self.buf) {
Ok(len) => {
@ -407,10 +381,7 @@ enum NameConversionError {
}
fn name_from_msg(env: &xous::MessageEnvelope) -> Result<&str, NameConversionError> {
let msg = env
.body
.memory_message()
.ok_or(NameConversionError::InvalidMessageType)?;
let msg = env.body.memory_message().ok_or(NameConversionError::InvalidMessageType)?;
let valid_bytes = msg.valid.map(|v| v.get()).unwrap_or_else(|| msg.buf.len());
if valid_bytes > DNS_NAME_LENGTH_LIMIT || valid_bytes < 1 {
log::error!("valid bytes exceeded DNS name limit");
@ -418,8 +389,7 @@ fn name_from_msg(env: &xous::MessageEnvelope) -> Result<&str, NameConversionErro
}
// Safe because we've already validated that it's a valid range
let str_slice = unsafe { core::slice::from_raw_parts(msg.buf.as_ptr(), valid_bytes) };
let name_string =
core::str::from_utf8(str_slice).map_err(|_| NameConversionError::InvalidString)?;
let name_string = core::str::from_utf8(str_slice).map_err(|_| NameConversionError::InvalidString)?;
Ok(name_string)
}
@ -494,9 +464,7 @@ fn main() -> ! {
time::start_time_ux();
let xns = xous_names::XousNames::new().unwrap();
let dns_sid = xns
.register_name(api::SERVER_NAME_DNS, None)
.expect("can't register server");
let dns_sid = xns.register_name(api::SERVER_NAME_DNS, None).expect("can't register server");
log::trace!("registered with NS -- {:?}", dns_sid);
// this will magically populate a list of DNS servers when they become available
@ -572,12 +540,9 @@ fn main() -> ! {
};
}
Some(Opcode::Lookup) => {
let mut buf = unsafe {
Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())
};
let name = buf
.to_original::<String<DNS_NAME_LENGTH_LIMIT>, _>()
.unwrap();
let mut buf =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let name = buf.to_original::<String<DNS_NAME_LENGTH_LIMIT>, _>().unwrap();
let name_std = std::string::String::from(name.as_str().unwrap());
if let Some(cache_entry) = dns_cache.get(&name_std) {
// pick a random entry
@ -617,30 +582,20 @@ fn main() -> ! {
}
} else {
// no names found
let response = DnsResponse {
addr: None,
code: DnsResponseCode::NameError,
};
let response = DnsResponse { addr: None, code: DnsResponseCode::NameError };
buf.replace(response).unwrap();
}
}
Err(e) => {
log::debug!("DNS query failed: {}->{:?}", name, e);
let response = DnsResponse {
addr: None,
code: e,
};
let response = DnsResponse { addr: None, code: e };
buf.replace(response).unwrap();
}
}
}
}
Some(Opcode::UpdateTtl) => msg_scalar_unpack!(msg, incr_secs, _, _, _, {
let increment = if incr_secs < u32::MAX as usize {
incr_secs as u32
} else {
u32::MAX
};
let increment = if incr_secs < u32::MAX as usize { incr_secs as u32 } else { u32::MAX };
if !resolver.get_freeze() {
let mut expired_names = Vec::<std::string::String>::new();
for (name, cache_map) in dns_cache.iter_mut() {

View file

@ -1,3 +1,7 @@
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::num::ParseIntError;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// The `time_server` is unique is that it is written for exclusive use by `libstd` to extract time.
///
/// It also has a single hook that is callable from the PDDB to initialize a time value once the
@ -22,21 +26,18 @@
/// read RTC, plus you get milliseconds resolution. Time "T" can be updated at any time by just
/// reading the RTC and noting the ticktimer offset at the point of reading.
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(any(feature="precursor", feature="renode"))]
use chrono::prelude::*;
use gam::modal::*;
#[cfg(any(feature = "precursor", feature = "renode"))]
use llio::*;
use pddb::PddbMountPoller;
use num_traits::*;
// imports for time ux
use locales::t;
use chrono::prelude::*;
use xous::{Message, send_message};
use gam::modal::*;
use num_traits::*;
use pddb::PddbMountPoller;
// ntp imports
use sntpc::{Error, NtpContext, NtpTimestampGenerator, NtpUdpSocket, Result};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::num::ParseIntError;
use xous::{send_message, Message};
/// This is a "well known name" used by `libstd` to connect to the time server
/// Anyone who wants to check if time has been initialized would use this name.
@ -97,30 +98,20 @@ struct StdTimestampGen {
}
impl NtpTimestampGenerator for StdTimestampGen {
fn init(&mut self) {
self.duration = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap();
self.duration =
std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap();
}
fn timestamp_sec(&self) -> u64 {
self.duration.as_secs()
}
fn timestamp_sec(&self) -> u64 { self.duration.as_secs() }
fn timestamp_subsec_micros(&self) -> u32 {
self.duration.subsec_micros()
}
fn timestamp_subsec_micros(&self) -> u32 { self.duration.subsec_micros() }
}
#[derive(Debug)]
struct UdpSocketWrapper(UdpSocket);
impl NtpUdpSocket for UdpSocketWrapper {
fn send_to<T: ToSocketAddrs>(
&self,
buf: &[u8],
addr: T,
) -> Result<usize> {
fn send_to<T: ToSocketAddrs>(&self, buf: &[u8], addr: T) -> Result<usize> {
match self.0.send_to(buf, addr) {
Ok(usize) => Ok(usize),
Err(_) => Err(Error::Network),
@ -134,7 +125,7 @@ impl NtpUdpSocket for UdpSocketWrapper {
}
}
}
#[cfg(any(feature="precursor", feature="renode"))]
#[cfg(any(feature = "precursor", feature = "renode"))]
pub fn reset_rtc(i2c: &mut llio::I2c, start_time: u64, tt: &ticktimer_server::Ticktimer) {
log::info!("performing rtc reset");
i2c.i2c_mutex_acquire();
@ -152,16 +143,16 @@ pub fn reset_rtc(i2c: &mut llio::I2c, start_time: u64, tt: &ticktimer_server::Ti
}
let reset_vals = [
0, // clear interrupts, normal run
0, // clear interrupts
RTC_PWR_MODE, // power mode
to_bcd((start_time & 0xFF) as u8 % 60), // seconds
to_bcd(((start_time >> 8) & 0xFF) as u8 % 60), // minutes
to_bcd(((start_time >> 16) & 0xFF) as u8 % 24), // hours
0, // clear interrupts, normal run
0, // clear interrupts
RTC_PWR_MODE, // power mode
to_bcd((start_time & 0xFF) as u8 % 60), // seconds
to_bcd(((start_time >> 8) & 0xFF) as u8 % 60), // minutes
to_bcd(((start_time >> 16) & 0xFF) as u8 % 24), // hours
to_bcd(((start_time >> 24) & 0xFF) as u8 % 28 + 1), // days
0, // sunday
0, // sunday
to_bcd(((start_time >> 32) & 0xFF) as u8 % 12 + 1), // months
to_bcd(((start_time >> 40) & 0xFF) as u8 % 5 + 1), // years
to_bcd(((start_time >> 40) & 0xFF) as u8 % 5 + 1), // years
];
i2c.i2c_mutex_acquire();
log::info!("writing: {:x?}", reset_vals);
@ -180,12 +171,12 @@ pub fn start_time_server() {
let rtc_checked = Arc::new(AtomicBool::new(false));
// the public SID is well known and accessible by anyone who uses `libstd`
let pub_sid = xous::create_server_with_address(&TIME_SERVER_PUBLIC)
.expect("Couldn't create Ticktimer server");
let pub_sid =
xous::create_server_with_address(&TIME_SERVER_PUBLIC).expect("Couldn't create Ticktimer server");
let self_cid = xous::connect(pub_sid).unwrap();
// this thread handles more sensitive operations on the RTC.
#[cfg(any(feature="precursor", feature="renode"))]
#[cfg(any(feature = "precursor", feature = "renode"))]
thread::spawn({
let rtc_checked = rtc_checked.clone();
let self_cid = self_cid.clone();
@ -205,8 +196,9 @@ pub fn start_time_server() {
Some(susres::SuspendOrder::Early),
&xns,
PrivTimeOp::SusRes as u32,
priv_conn
).expect("couldn't create suspend/resume object");
priv_conn,
)
.expect("couldn't create suspend/resume object");
// on boot, do the validation checks of the RTC. If it is not initialized or corrupted, fix it.
let mut settings = [0u8; 8];
@ -215,13 +207,13 @@ pub fn start_time_server() {
match i2c.i2c_read_no_repeated_start(ABRTCMC_I2C_ADR, ABRTCMC_CONTROL3, &mut settings) {
Ok(I2cStatus::ResponseReadOk) => {
i2c.i2c_mutex_release();
break
},
break;
}
_ => {
i2c.i2c_mutex_release();
log::error!("Couldn't check RTC, retrying!");
xous::yield_slice(); // recheck in a fast loop, we really should be able to grab this resource on boot.
},
}
};
}
if is_rtc_invalid(&settings) {
@ -239,11 +231,14 @@ pub fn start_time_server() {
}),
Some(PrivTimeOp::SusRes) => xous::msg_scalar_unpack!(msg, token, _, _, _, {
susres.suspend_until_resume(token).expect("couldn't execute suspend/resume");
// resync time on resume, but give a little time for other processes to clear as this is not urgent
// resync time on resume, but give a little time for other processes to clear as this
// is not urgent
tt.sleep_ms(180).unwrap();
send_message(self_cid,
Message::new_scalar(TimeOp::HwSync.to_usize().unwrap(), 0, 0, 0, 0)
).expect("couldn't queue sync request");
send_message(
self_cid,
Message::new_scalar(TimeOp::HwSync.to_usize().unwrap(), 0, 0, 0, 0),
)
.expect("couldn't queue sync request");
}),
_ => log::error!("Time server private thread received unknown opcode: {:?}", msg),
}
@ -257,7 +252,7 @@ pub fn start_time_server() {
move || {
let xns = xous_names::XousNames::new().unwrap();
let llio = llio::Llio::new(&xns);
#[cfg(not(feature="minimal-testing"))]
#[cfg(not(feature = "minimal-testing"))]
let prefs = userprefs::Manager::new();
let tt = ticktimer_server::Ticktimer::new().unwrap();
@ -266,7 +261,8 @@ pub fn start_time_server() {
tt.sleep_ms(42).unwrap();
}
let mut start_rtc_secs: u64;
// this loop allows us to fail slightly more gracefully in the case that there is an RTC hardware failure
// this loop allows us to fail slightly more gracefully in the case that there is an RTC hardware
// failure
loop {
match llio.get_rtc_secs() {
Ok(s) => {
@ -283,17 +279,19 @@ pub fn start_time_server() {
log::trace!("start_rtc_secs: {}", start_rtc_secs);
log::trace!("start_tt_ms: {}", start_tt_ms);
#[cfg(not(feature="minimal-testing"))]
#[cfg(not(feature = "minimal-testing"))]
let pddb_poller = PddbMountPoller::new();
// enqueue a the first mount poll message
#[cfg(not(feature="minimal-testing"))]
xous::send_message(self_cid,
xous::Message::new_scalar(TimeOp::PddbMountPoll.to_usize().unwrap(), 0, 0, 0, 0)
).expect("couldn't check mount poll");
#[cfg(not(feature = "minimal-testing"))]
xous::send_message(
self_cid,
xous::Message::new_scalar(TimeOp::PddbMountPoll.to_usize().unwrap(), 0, 0, 0, 0),
)
.expect("couldn't check mount poll");
// an initial behavior which just retuns the raw RTC time, until the PDDB is mounted.
let mut temp = 0;
loop {
#[cfg(not(feature="minimal-testing"))]
#[cfg(not(feature = "minimal-testing"))]
if pddb_poller.is_mounted_nonblocking() {
log::debug!("PDDB mount detected, transitioning to real-time adjusted server");
break;
@ -308,35 +306,37 @@ pub fn start_time_server() {
log::trace!("mount poll");
}
temp += 1;
xous::send_message(self_cid,
xous::Message::new_scalar(TimeOp::PddbMountPoll.to_usize().unwrap(), 0, 0, 0, 0)
).expect("couldn't check mount poll");
xous::send_message(
self_cid,
xous::Message::new_scalar(TimeOp::PddbMountPoll.to_usize().unwrap(), 0, 0, 0, 0),
)
.expect("couldn't check mount poll");
}
Some(TimeOp::HwSync) => {
start_rtc_secs = llio.get_rtc_secs().expect("couldn't read RTC offset value");
start_tt_ms = tt.elapsed_ms();
},
}
Some(TimeOp::GetUtcTimeMs) => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
let t =
start_rtc_secs as i64 * 1000i64
+ (tt.elapsed_ms() - start_tt_ms) as i64;
let t = start_rtc_secs as i64 * 1000i64 + (tt.elapsed_ms() - start_tt_ms) as i64;
log::debug!("hw only UTC ms {}", t);
// NOTE: endian swap for compatibility with `std`
xous::return_scalar2(msg.sender,
xous::return_scalar2(
msg.sender,
(t as u64 & 0xFFFF_FFFF) as usize,
(((t as u64) >> 32) & 0xFFFF_FFFF) as usize,
).expect("couldn't respond to GetUtcTimeMs");
)
.expect("couldn't respond to GetUtcTimeMs");
}),
Some(TimeOp::GetLocalTimeMs) => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
let t =
start_rtc_secs as i64 * 1000i64
+ (tt.elapsed_ms() - start_tt_ms) as i64;
let t = start_rtc_secs as i64 * 1000i64 + (tt.elapsed_ms() - start_tt_ms) as i64;
assert!(t > 0, "time result is negative, this is an error");
log::debug!("hw only local ms {}", t);
xous::return_scalar2(msg.sender,
xous::return_scalar2(
msg.sender,
(((t as u64) >> 32) & 0xFFFF_FFFF) as usize,
(t as u64 & 0xFFFF_FFFF) as usize,
).expect("couldn't respond to GetLocalTimeMs");
)
.expect("couldn't respond to GetLocalTimeMs");
}),
Some(TimeOp::WallClockTimeInit) => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
// definitely not initialized
@ -345,19 +345,22 @@ pub fn start_time_server() {
_ => log::warn!("Time server can't handle this message yet: {:?}", msg),
}
}
#[cfg(not(feature="minimal-testing"))]
#[cfg(not(feature = "minimal-testing"))]
let mut utc_offset_ms = prefs.utc_offset().unwrap_or_else(|error| {
log::error!("cannot read utc offset: {:?}", error);
0
});
#[cfg(not(feature="minimal-testing"))]
let mut tz_offset_ms = prefs.timezone_offset().unwrap_or_else(|error| {
log::error!("cannot read timezone offset: {:?}", error);
None
}).unwrap_or_default();
#[cfg(feature="minimal-testing")]
#[cfg(not(feature = "minimal-testing"))]
let mut tz_offset_ms = prefs
.timezone_offset()
.unwrap_or_else(|error| {
log::error!("cannot read timezone offset: {:?}", error);
None
})
.unwrap_or_default();
#[cfg(feature = "minimal-testing")]
let mut utc_offset_ms = 0;
#[cfg(feature="minimal-testing")]
#[cfg(feature = "minimal-testing")]
let mut tz_offset_ms = 0;
log::debug!("offset_key: {}", utc_offset_ms / 1000);
@ -372,86 +375,96 @@ pub fn start_time_server() {
Some(TimeOp::PddbMountPoll) => {
// do nothing, we're mounted now.
continue;
},
Some(TimeOp::HwSync) => {
match llio.get_rtc_secs() {
Ok(val) => {
start_rtc_secs = val;
start_tt_ms = tt.elapsed_ms();
}
Err(e) => {
log::warn!("Error syncing time: {:?}; retrying!", e);
tt.sleep_ms(82).unwrap();
send_message(self_cid,
Message::new_scalar(TimeOp::HwSync.to_usize().unwrap(), 0, 0, 0, 0)
).expect("couldn't queue sync request");
}
}
Some(TimeOp::HwSync) => match llio.get_rtc_secs() {
Ok(val) => {
start_rtc_secs = val;
start_tt_ms = tt.elapsed_ms();
}
Err(e) => {
log::warn!("Error syncing time: {:?}; retrying!", e);
tt.sleep_ms(82).unwrap();
send_message(
self_cid,
Message::new_scalar(TimeOp::HwSync.to_usize().unwrap(), 0, 0, 0, 0),
)
.expect("couldn't queue sync request");
}
},
Some(TimeOp::GetUtcTimeMs) => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
let t =
start_rtc_secs as i64 * 1000i64
let t = start_rtc_secs as i64 * 1000i64
+ (tt.elapsed_ms() - start_tt_ms) as i64
+ utc_offset_ms;
if t < 0 { // the offset has some error in it, perhaps due to an RTC reset. reset the offset!
log::warn!("Time was negative, recovering from time setting error by clearing utc offset to 0");
if t < 0 {
// the offset has some error in it, perhaps due to an RTC reset. reset the offset!
log::warn!(
"Time was negative, recovering from time setting error by clearing utc offset to 0"
);
prefs.set_utc_offset(0).ok();
utc_offset_ms = 0;
}
log::trace!("utc ms {}", t);
// NOTE: endian swap for compatibility with `std`
xous::return_scalar2(msg.sender,
xous::return_scalar2(
msg.sender,
(t as u64 & 0xFFFF_FFFF) as usize,
(((t as u64) >> 32) & 0xFFFF_FFFF) as usize,
).expect("couldn't respond to GetUtcTimeMs");
)
.expect("couldn't respond to GetUtcTimeMs");
}),
Some(TimeOp::GetLocalTimeMs) => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
log::trace!("current offset {}", (start_rtc_secs as i64 * 1000i64 + (tt.elapsed_ms() - start_tt_ms) as i64) / 1000);
let t =
start_rtc_secs as i64 * 1000i64
log::trace!(
"current offset {}",
(start_rtc_secs as i64 * 1000i64 + (tt.elapsed_ms() - start_tt_ms) as i64) / 1000
);
let t = start_rtc_secs as i64 * 1000i64
+ (tt.elapsed_ms() - start_tt_ms) as i64
+ utc_offset_ms
+ tz_offset_ms;
if t < 0 {
log::warn!("Time was negative, recovering from time setting error by clearing utc and timezone offsets to 0.");
log::warn!(
"Time was negative, recovering from time setting error by clearing utc and timezone offsets to 0."
);
prefs.set_utc_offset(0).ok();
prefs.set_timezone_offset(0).ok();
utc_offset_ms = 0;
tz_offset_ms = 0;
}
log::trace!("local since epoch {}", t / 1000);
xous::return_scalar2(msg.sender,
xous::return_scalar2(
msg.sender,
(((t as u64) >> 32) & 0xFFFF_FFFF) as usize,
(t as u64 & 0xFFFF_FFFF) as usize,
).expect("couldn't respond to GetLocalTimeMs");
}),
Some(TimeOp::SetUtcTimeMs) => xous::msg_scalar_unpack!(msg, utc_hi_ms, utc_lo_ms, _, _, {
let utc_time_ms = (utc_hi_ms as i64) << 32 | (utc_lo_ms as i64);
start_rtc_secs = llio.get_rtc_secs().expect("couldn't read RTC offset value");
start_tt_ms = tt.elapsed_ms();
log::info!("utc_time: {}", utc_time_ms / 1000);
log::info!("rtc_secs: {}", start_rtc_secs);
log::info!("start_tt_ms: {}", start_tt_ms);
let offset =
utc_time_ms -
(start_rtc_secs as i64) * 1000;
utc_offset_ms = offset;
#[cfg(not(feature="minimal-testing"))]
prefs.set_utc_offset(offset).unwrap_or_else(|err| {
log::error!("cannot set utc offset: {:?}", err);
});
)
.expect("couldn't respond to GetLocalTimeMs");
}),
Some(TimeOp::SetUtcTimeMs) => {
xous::msg_scalar_unpack!(msg, utc_hi_ms, utc_lo_ms, _, _, {
let utc_time_ms = (utc_hi_ms as i64) << 32 | (utc_lo_ms as i64);
start_rtc_secs = llio.get_rtc_secs().expect("couldn't read RTC offset value");
start_tt_ms = tt.elapsed_ms();
log::info!("utc_time: {}", utc_time_ms / 1000);
log::info!("rtc_secs: {}", start_rtc_secs);
log::info!("start_tt_ms: {}", start_tt_ms);
let offset = utc_time_ms - (start_rtc_secs as i64) * 1000;
utc_offset_ms = offset;
#[cfg(not(feature = "minimal-testing"))]
prefs.set_utc_offset(offset).unwrap_or_else(|err| {
log::error!("cannot set utc offset: {:?}", err);
});
})
}
Some(TimeOp::SetTzOffsetMs) => xous::msg_scalar_unpack!(msg, tz_hi_ms, tz_lo_ms, _, _, {
let tz_ms = ((tz_hi_ms as i64) << 32) | (tz_lo_ms as i64);
// sanity check with very broad bounds: I don't know of any time zones that are more than +/2 days from UTC
// 86400 seconds in a day, times 1000 milliseconds, times 2 days
if tz_ms < -(86400 * 1000 * 2) ||
tz_ms > 86400 * 1000 * 2 {
// sanity check with very broad bounds: I don't know of any time zones that are more
// than +/2 days from UTC 86400 seconds in a day, times 1000
// milliseconds, times 2 days
if tz_ms < -(86400 * 1000 * 2) || tz_ms > 86400 * 1000 * 2 {
log::warn!("Requested timezone offset {} is out of bounds, ignoring!", tz_ms);
continue;
} else {
tz_offset_ms = tz_ms;
#[cfg(not(feature="minimal-testing"))]
#[cfg(not(feature = "minimal-testing"))]
prefs.set_timezone_offset(tz_ms).unwrap_or_else(|err| {
log::error!("cannot set timezone offset: {:?}", err);
});
@ -505,9 +518,7 @@ fn is_rtc_invalid(settings: &[u8]) -> bool {
}
#[allow(dead_code)]
fn to_binary(bcd: u8) -> u8 {
(bcd & 0xf) + ((bcd >> 4) * 10)
}
fn to_binary(bcd: u8) -> u8 { (bcd & 0xf) + ((bcd >> 4) * 10) }
#[allow(dead_code)]
fn to_bcd(binary: u8) -> u8 {
let mut lsd: u8 = binary % 10;
@ -542,7 +553,7 @@ fn simple_kilofloat_parse(input: &str) -> core::result::Result<i32, ParseIntErro
}
significance /= 10;
} else {
return "z".parse::<i32>() // you can't create a ParseIntError any other way
return "z".parse::<i32>(); // you can't create a ParseIntError any other way
}
}
Ok(result)
@ -552,14 +563,15 @@ fn simple_kilofloat_parse(input: &str) -> core::result::Result<i32, ParseIntErro
}
}
pub (crate) fn start_time_ux() {
pub(crate) fn start_time_ux() {
thread::spawn({
move || {
// some RTC UX structures
let xns = xous_names::XousNames::new().unwrap();
let sid = xns.register_name(crate::TIME_UX_NAME, Some(1)).unwrap();
let modals = modals::Modals::new(&xns).unwrap();
let timeserver_cid = xous::connect(xous::SID::from_bytes(crate::time::TIME_SERVER_PUBLIC).unwrap()).unwrap();
let timeserver_cid =
xous::connect(xous::SID::from_bytes(crate::time::TIME_SERVER_PUBLIC).unwrap()).unwrap();
let pddb_poller = pddb::PddbMountPoller::new();
let trng = trng::Trng::new(&xns).unwrap();
@ -570,7 +582,9 @@ pub (crate) fn start_time_ux() {
match FromPrimitive::from_usize(msg.body.id()) {
Some(crate::TimeUxOp::SetTime) => xous::msg_scalar_unpack!(msg, _, _, _, _, {
if !pddb_poller.is_mounted_nonblocking() {
modals.show_notification(t!("stats.please_mount", locales::LANG), None).expect("couldn't show notification");
modals
.show_notification(t!("stats.please_mount", locales::LANG), None)
.expect("couldn't show notification");
continue;
}
let mut tz_set = false;
@ -579,7 +593,7 @@ pub (crate) fn start_time_ux() {
Some(data) => {
tz_set = true;
data
},
}
None => {
tz_set = false;
0i64
@ -595,40 +609,52 @@ pub (crate) fn start_time_ux() {
// a key exists, but nothing was written to it (length of key was 0 or inappropriate)
if !tz_set {
log::info!("{}RTC.TZ,{}", xous::BOOKEND_START, xous::BOOKEND_END);
let tz_str = modals.alert_builder(t!("rtc.timezone", locales::LANG))
let tz_str = modals
.alert_builder(t!("rtc.timezone", locales::LANG))
.field(None, Some(tz_ux_validator))
.build()
.expect("couldn't get timezone")
.first();
let tz = simple_kilofloat_parse(tz_str.as_str()).expect("pre-validated input failed to re-parse!");
let tz = simple_kilofloat_parse(tz_str.as_str())
.expect("pre-validated input failed to re-parse!");
log::info!("got tz offset {}", tz);
tz_offset_ms = (tz * 3600) as i64;
xous::send_message(timeserver_cid,
xous::send_message(
timeserver_cid,
Message::new_scalar(
crate::time::TimeOp::SetTzOffsetMs.to_usize().unwrap(),
(tz_offset_ms >> 32) as usize,
(tz_offset_ms & 0xFFFF_FFFF) as usize,
0, 0,
)
).expect("couldn't set timezone");
0,
0,
),
)
.expect("couldn't set timezone");
}
// see if we want to try to use NTP or not
log::info!("{}RTC.NTP,{}", xous::BOOKEND_START, xous::BOOKEND_END);
modals.add_list_item(t!("pddb.yes", locales::LANG)).expect("couldn't build radio item list");
modals.add_list_item(t!("pddb.no", locales::LANG)).expect("couldn't build radio item list");
modals
.add_list_item(t!("pddb.yes", locales::LANG))
.expect("couldn't build radio item list");
modals
.add_list_item(t!("pddb.no", locales::LANG))
.expect("couldn't build radio item list");
let mut try_ntp = true;
match modals.get_radiobutton(t!("rtc.try_ntp", locales::LANG)) {
Ok(selection) => {
if selection == t!("pddb.no", locales::LANG) {
try_ntp = false;
}
},
}
_ => log::error!("get_radiobutton failed"),
}
if try_ntp {
let local_port = (trng.get_u32().unwrap() % 16384 + 49152) as u16;
let socket_addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), local_port);
let socket_addr = SocketAddr::new(
std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
local_port,
);
let socket = UdpSocket::bind(socket_addr).expect("Unable to create UDP socket");
log::debug!("NTP rx socket created {:?}", socket);
socket
@ -640,24 +666,30 @@ pub (crate) fn start_time_ux() {
match result {
Ok(time) => {
log::info!("Got NTP time: {}.{}", time.sec(), time.sec_fraction());
let current_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + chrono::Duration::seconds(time.sec() as i64);
let current_time = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)
+ chrono::Duration::seconds(time.sec() as i64);
log::info!("Setting UTC time: {:?}", current_time.to_string());
xous::send_message(timeserver_cid,
xous::send_message(
timeserver_cid,
Message::new_scalar(
crate::time::TimeOp::SetUtcTimeMs.to_usize().unwrap(),
((current_time.timestamp_millis() as u64) >> 32) as usize,
(current_time.timestamp_millis() as u64 & 0xFFFF_FFFF) as usize,
0, 0,
)
).expect("couldn't set time");
0,
0,
),
)
.expect("couldn't set time");
log::info!("{}RTC.NTPOK,{}", xous::BOOKEND_START, xous::BOOKEND_END);
continue;
}
Err(err) => {
log::info!("Err: {:?}", err);
log::info!("{}RTC.NTPFAIL,{}", xous::BOOKEND_START, xous::BOOKEND_END);
modals.show_notification(t!("rtc.ntp_fail", locales::LANG), None).expect("couldn't show NTP error");
},
modals
.show_notification(t!("rtc.ntp_fail", locales::LANG), None)
.expect("couldn't show NTP error");
}
}
}
@ -668,18 +700,38 @@ pub (crate) fn start_time_ux() {
let mut days: u8 = 0;
let mut years: u8 = 0;
let date = modals.alert_builder(t!("rtc.set_time_modal", locales::LANG))
.field(Some(String::from(t!("rtc.month", locales::LANG))), Some(rtc_ux_validate_month))
.field(Some(String::from(t!("rtc.day", locales::LANG))), Some(rtc_ux_validate_day))
.field(Some(String::from(t!("rtc.year", locales::LANG))), Some(rtc_ux_validate_year))
.field(Some(String::from(t!("rtc.hour", locales::LANG))), Some(rtc_ux_validate_hour))
.field(Some(String::from(t!("rtc.minute", locales::LANG))), Some(rtc_ux_validate_minute))
.field(Some(String::from(t!("rtc.seconds", locales::LANG))), Some(rtc_ux_validate_seconds))
let date = modals
.alert_builder(t!("rtc.set_time_modal", locales::LANG))
.field(
Some(String::from(t!("rtc.month", locales::LANG))),
Some(rtc_ux_validate_month),
)
.field(
Some(String::from(t!("rtc.day", locales::LANG))),
Some(rtc_ux_validate_day),
)
.field(
Some(String::from(t!("rtc.year", locales::LANG))),
Some(rtc_ux_validate_year),
)
.field(
Some(String::from(t!("rtc.hour", locales::LANG))),
Some(rtc_ux_validate_hour),
)
.field(
Some(String::from(t!("rtc.minute", locales::LANG))),
Some(rtc_ux_validate_minute),
)
.field(
Some(String::from(t!("rtc.seconds", locales::LANG))),
Some(rtc_ux_validate_seconds),
)
.build()
.expect("cannot get date from user");
for (index, elem) in date.content().iter().enumerate() {
let elem = elem.as_str().parse::<u8>().expect("pre-validated input failed to re-parse!");
let elem =
elem.as_str().parse::<u8>().expect("pre-validated input failed to re-parse!");
match index {
0 => months = elem,
1 => days = elem,
@ -687,29 +739,36 @@ pub (crate) fn start_time_ux() {
3 => hours = elem,
4 => mins = elem,
5 => secs = elem,
_ => {},
_ => {}
}
}
log::info!("Setting time: {}/{}/{} {}:{}:{}", months, days, years, hours, mins, secs);
let new_dt = chrono::FixedOffset::east((tz_offset_ms / 1000) as i32).ymd(years as i32 + 2000, months as u32, days as u32)
.and_hms(hours as u32, mins as u32, secs as u32);
xous::send_message(timeserver_cid,
let new_dt = chrono::FixedOffset::east((tz_offset_ms / 1000) as i32)
.ymd(years as i32 + 2000, months as u32, days as u32)
.and_hms(hours as u32, mins as u32, secs as u32);
xous::send_message(
timeserver_cid,
Message::new_scalar(
crate::time::TimeOp::SetUtcTimeMs.to_usize().unwrap(),
((new_dt.timestamp_millis() as u64) >> 32) as usize,
(new_dt.timestamp_millis() as u64 & 0xFFFF_FFFF) as usize,
0, 0,
)
).expect("couldn't set time");
0,
0,
),
)
.expect("couldn't set time");
}),
Some(crate::TimeUxOp::SetTimeZone) => xous::msg_scalar_unpack!(msg, _, _, _, _, {
if !pddb_poller.is_mounted_nonblocking() {
modals.show_notification(t!("stats.please_mount", locales::LANG), None).expect("couldn't show notification");
modals
.show_notification(t!("stats.please_mount", locales::LANG), None)
.expect("couldn't show notification");
continue;
}
let tz_str = modals.alert_builder(t!("rtc.timezone", locales::LANG))
let tz_str = modals
.alert_builder(t!("rtc.timezone", locales::LANG))
.field(None, Some(tz_ux_validator))
.build()
.expect("couldn't get timezone")
@ -718,14 +777,17 @@ pub (crate) fn start_time_ux() {
.expect("pre-validated input failed to re-parse!");
log::info!("got tz offset {}", tz);
let tzoff_ms = (tz * 3600) as i64;
xous::send_message(timeserver_cid,
xous::send_message(
timeserver_cid,
Message::new_scalar(
crate::time::TimeOp::SetTzOffsetMs.to_usize().unwrap(),
(tzoff_ms >> 32) as usize,
(tzoff_ms & 0xFFFF_FFFF) as usize,
0, 0,
)
).expect("couldn't set timezone");
0,
0,
),
)
.expect("couldn't set timezone");
}),
Some(crate::TimeUxOp::Quit) => {
xous::return_scalar(msg.sender, 0).unwrap();
@ -756,9 +818,11 @@ fn tz_ux_validator(input: TextEntryPayload) -> Option<ValidatorErr> {
let text_str = input.as_str();
match simple_kilofloat_parse(text_str) {
Ok(input) => if input < -12_000 || input > 14_000 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
},
Ok(input) => {
if input < -12_000 || input > 14_000 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
}
_ => return Some(ValidatorErr::from_str(t!("rtc.integer_err", locales::LANG))),
}
None
@ -773,7 +837,7 @@ fn rtc_ux_validate_month(input: TextEntryPayload) -> Option<ValidatorErr> {
};
if input < 1 || input > 12 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)))
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
None
@ -788,7 +852,7 @@ fn rtc_ux_validate_day(input: TextEntryPayload) -> Option<ValidatorErr> {
};
if input < 1 || input > 31 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)))
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
None
@ -803,7 +867,7 @@ fn rtc_ux_validate_year(input: TextEntryPayload) -> Option<ValidatorErr> {
};
if input > 99 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)))
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
None
@ -818,7 +882,7 @@ fn rtc_ux_validate_hour(input: TextEntryPayload) -> Option<ValidatorErr> {
};
if input > 23 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)))
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
None
@ -833,7 +897,7 @@ fn rtc_ux_validate_minute(input: TextEntryPayload) -> Option<ValidatorErr> {
};
if input > 59 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)))
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
None
@ -848,9 +912,8 @@ fn rtc_ux_validate_seconds(input: TextEntryPayload) -> Option<ValidatorErr> {
};
if input > 59 {
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)))
return Some(ValidatorErr::from_str(t!("rtc.range_err", locales::LANG)));
}
None
}