feat: SNTP crate #4

merged
opened by sachy.dev targeting main from more-pico-crates
Changed files
+465 -11
sachy-fmt
sachy-sntp
+220 -8
Cargo.lock
··· 48 source = "registry+https://github.com/rust-lang/crates.io-index" 49 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 50 51 [[package]] 52 name = "core-foundation" 53 version = "0.10.0" ··· 64 source = "registry+https://github.com/rust-lang/crates.io-index" 65 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 66 67 [[package]] 68 name = "defmt" 69 version = "0.3.100" ··· 105 "thiserror", 106 ] 107 108 [[package]] 109 name = "embedded-hal" 110 version = "1.0.0" ··· 120 source = "registry+https://github.com/rust-lang/crates.io-index" 121 checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 122 dependencies = [ 123 - "embedded-hal", 124 ] 125 126 [[package]] ··· 129 source = "registry+https://github.com/rust-lang/crates.io-index" 130 checksum = "f9a0f04f8886106faf281c47b6a0e4054a369baedaf63591fdb8da9761f3f379" 131 dependencies = [ 132 - "embedded-hal", 133 "embedded-hal-nb", 134 ] 135 ··· 139 source = "registry+https://github.com/rust-lang/crates.io-index" 140 checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" 141 dependencies = [ 142 - "embedded-hal", 143 - "nb", 144 ] 145 146 [[package]] ··· 149 source = "registry+https://github.com/rust-lang/crates.io-index" 150 checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 151 152 [[package]] 153 name = "gpio-cdev" 154 version = "0.6.0" ··· 169 "byteorder", 170 ] 171 172 [[package]] 173 name = "heapless" 174 version = "0.9.2" ··· 215 checksum = "2a8a605c95f708c78554738a12153b213f107d3bd5323f7ce32d6deb3faafb40" 216 dependencies = [ 217 "cast", 218 - "embedded-hal", 219 "embedded-hal-nb", 220 "gpio-cdev", 221 "i2cdev", 222 - "nb", 223 "nix 0.27.1", 224 "serialport", 225 "spidev", 226 "sysfs_gpio", 227 ] 228 229 [[package]] 230 name = "mach2" 231 version = "0.4.3" ··· 235 "libc", 236 ] 237 238 [[package]] 239 name = "memoffset" 240 version = "0.6.5" ··· 253 "autocfg", 254 ] 255 256 [[package]] 257 name = "nb" 258 version = "1.1.0" ··· 296 "libc", 297 ] 298 299 [[package]] 300 name = "pin-utils" 301 version = "0.1.0" ··· 351 version = "0.1.0" 352 dependencies = [ 353 "defmt 1.0.1", 354 - "heapless", 355 "sachy-fmt", 356 ] 357 ··· 371 version = "0.1.0" 372 dependencies = [ 373 "defmt 1.0.1", 374 - "embedded-hal", 375 "embedded-hal-async", 376 "embedded-hal-mock", 377 "linux-embedded-hal", 378 ] 379 380 [[package]] 381 name = "scopeguard" 382 version = "1.2.0" ··· 408 source = "registry+https://github.com/rust-lang/crates.io-index" 409 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 410 411 [[package]] 412 name = "spidev" 413 version = "0.6.1" ··· 480 source = "registry+https://github.com/rust-lang/crates.io-index" 481 checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 482 483 [[package]] 484 name = "windows-sys" 485 version = "0.52.0"
··· 48 source = "registry+https://github.com/rust-lang/crates.io-index" 49 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 50 51 + [[package]] 52 + name = "chrono" 53 + version = "0.4.42" 54 + source = "registry+https://github.com/rust-lang/crates.io-index" 55 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 56 + dependencies = [ 57 + "num-traits", 58 + ] 59 + 60 [[package]] 61 name = "core-foundation" 62 version = "0.10.0" ··· 73 source = "registry+https://github.com/rust-lang/crates.io-index" 74 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 75 76 + [[package]] 77 + name = "critical-section" 78 + version = "1.2.0" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 81 + 82 [[package]] 83 name = "defmt" 84 version = "0.3.100" ··· 120 "thiserror", 121 ] 122 123 + [[package]] 124 + name = "document-features" 125 + version = "0.2.12" 126 + source = "registry+https://github.com/rust-lang/crates.io-index" 127 + checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" 128 + dependencies = [ 129 + "litrs", 130 + ] 131 + 132 + [[package]] 133 + name = "embassy-net" 134 + version = "0.7.1" 135 + source = "registry+https://github.com/rust-lang/crates.io-index" 136 + checksum = "0558a231a47e7d4a06a28b5278c92e860f1200f24821d2f365a2f40fe3f3c7b2" 137 + dependencies = [ 138 + "document-features", 139 + "embassy-net-driver", 140 + "embassy-sync", 141 + "embassy-time", 142 + "embedded-io-async", 143 + "embedded-nal-async", 144 + "heapless 0.8.0", 145 + "managed", 146 + "smoltcp", 147 + ] 148 + 149 + [[package]] 150 + name = "embassy-net-driver" 151 + version = "0.2.0" 152 + source = "registry+https://github.com/rust-lang/crates.io-index" 153 + checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" 154 + 155 + [[package]] 156 + name = "embassy-sync" 157 + version = "0.7.2" 158 + source = "registry+https://github.com/rust-lang/crates.io-index" 159 + checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" 160 + dependencies = [ 161 + "cfg-if", 162 + "critical-section", 163 + "embedded-io-async", 164 + "futures-core", 165 + "futures-sink", 166 + "heapless 0.8.0", 167 + ] 168 + 169 + [[package]] 170 + name = "embassy-time" 171 + version = "0.5.0" 172 + source = "registry+https://github.com/rust-lang/crates.io-index" 173 + checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" 174 + dependencies = [ 175 + "cfg-if", 176 + "critical-section", 177 + "document-features", 178 + "embassy-time-driver", 179 + "embedded-hal 0.2.7", 180 + "embedded-hal 1.0.0", 181 + "embedded-hal-async", 182 + "futures-core", 183 + ] 184 + 185 + [[package]] 186 + name = "embassy-time-driver" 187 + version = "0.2.1" 188 + source = "registry+https://github.com/rust-lang/crates.io-index" 189 + checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" 190 + dependencies = [ 191 + "document-features", 192 + ] 193 + 194 + [[package]] 195 + name = "embedded-hal" 196 + version = "0.2.7" 197 + source = "registry+https://github.com/rust-lang/crates.io-index" 198 + checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 199 + dependencies = [ 200 + "nb 0.1.3", 201 + "void", 202 + ] 203 + 204 [[package]] 205 name = "embedded-hal" 206 version = "1.0.0" ··· 216 source = "registry+https://github.com/rust-lang/crates.io-index" 217 checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 218 dependencies = [ 219 + "embedded-hal 1.0.0", 220 ] 221 222 [[package]] ··· 225 source = "registry+https://github.com/rust-lang/crates.io-index" 226 checksum = "f9a0f04f8886106faf281c47b6a0e4054a369baedaf63591fdb8da9761f3f379" 227 dependencies = [ 228 + "embedded-hal 1.0.0", 229 "embedded-hal-nb", 230 ] 231 ··· 235 source = "registry+https://github.com/rust-lang/crates.io-index" 236 checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" 237 dependencies = [ 238 + "embedded-hal 1.0.0", 239 + "nb 1.1.0", 240 + ] 241 + 242 + [[package]] 243 + name = "embedded-io" 244 + version = "0.6.1" 245 + source = "registry+https://github.com/rust-lang/crates.io-index" 246 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 247 + 248 + [[package]] 249 + name = "embedded-io-async" 250 + version = "0.6.1" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" 253 + dependencies = [ 254 + "embedded-io", 255 + ] 256 + 257 + [[package]] 258 + name = "embedded-nal" 259 + version = "0.9.0" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "c56a28be191a992f28f178ec338a0bf02f63d7803244add736d026a471e6ed77" 262 + dependencies = [ 263 + "nb 1.1.0", 264 + ] 265 + 266 + [[package]] 267 + name = "embedded-nal-async" 268 + version = "0.8.0" 269 + source = "registry+https://github.com/rust-lang/crates.io-index" 270 + checksum = "76959917cd2b86f40a98c28dd5624eddd1fa69d746241c8257eac428d83cb211" 271 + dependencies = [ 272 + "embedded-io-async", 273 + "embedded-nal", 274 ] 275 276 [[package]] ··· 279 source = "registry+https://github.com/rust-lang/crates.io-index" 280 checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 281 282 + [[package]] 283 + name = "futures-core" 284 + version = "0.3.31" 285 + source = "registry+https://github.com/rust-lang/crates.io-index" 286 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 287 + 288 + [[package]] 289 + name = "futures-sink" 290 + version = "0.3.31" 291 + source = "registry+https://github.com/rust-lang/crates.io-index" 292 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 293 + 294 [[package]] 295 name = "gpio-cdev" 296 version = "0.6.0" ··· 311 "byteorder", 312 ] 313 314 + [[package]] 315 + name = "heapless" 316 + version = "0.8.0" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 319 + dependencies = [ 320 + "hash32", 321 + "stable_deref_trait", 322 + ] 323 + 324 [[package]] 325 name = "heapless" 326 version = "0.9.2" ··· 367 checksum = "2a8a605c95f708c78554738a12153b213f107d3bd5323f7ce32d6deb3faafb40" 368 dependencies = [ 369 "cast", 370 + "embedded-hal 1.0.0", 371 "embedded-hal-nb", 372 "gpio-cdev", 373 "i2cdev", 374 + "nb 1.1.0", 375 "nix 0.27.1", 376 "serialport", 377 "spidev", 378 "sysfs_gpio", 379 ] 380 381 + [[package]] 382 + name = "litrs" 383 + version = "1.0.0" 384 + source = "registry+https://github.com/rust-lang/crates.io-index" 385 + checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" 386 + 387 [[package]] 388 name = "mach2" 389 version = "0.4.3" ··· 393 "libc", 394 ] 395 396 + [[package]] 397 + name = "managed" 398 + version = "0.8.0" 399 + source = "registry+https://github.com/rust-lang/crates.io-index" 400 + checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" 401 + 402 [[package]] 403 name = "memoffset" 404 version = "0.6.5" ··· 417 "autocfg", 418 ] 419 420 + [[package]] 421 + name = "nb" 422 + version = "0.1.3" 423 + source = "registry+https://github.com/rust-lang/crates.io-index" 424 + checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 425 + dependencies = [ 426 + "nb 1.1.0", 427 + ] 428 + 429 [[package]] 430 name = "nb" 431 version = "1.1.0" ··· 469 "libc", 470 ] 471 472 + [[package]] 473 + name = "num-traits" 474 + version = "0.2.19" 475 + source = "registry+https://github.com/rust-lang/crates.io-index" 476 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 477 + dependencies = [ 478 + "autocfg", 479 + ] 480 + 481 [[package]] 482 name = "pin-utils" 483 version = "0.1.0" ··· 533 version = "0.1.0" 534 dependencies = [ 535 "defmt 1.0.1", 536 + "heapless 0.9.2", 537 "sachy-fmt", 538 ] 539 ··· 553 version = "0.1.0" 554 dependencies = [ 555 "defmt 1.0.1", 556 + "embedded-hal 1.0.0", 557 "embedded-hal-async", 558 "embedded-hal-mock", 559 "linux-embedded-hal", 560 ] 561 562 + [[package]] 563 + name = "sachy-sntp" 564 + version = "0.1.0" 565 + dependencies = [ 566 + "chrono", 567 + "defmt 1.0.1", 568 + "embassy-net", 569 + "embassy-time", 570 + "sachy-fmt", 571 + ] 572 + 573 [[package]] 574 name = "scopeguard" 575 version = "1.2.0" ··· 601 source = "registry+https://github.com/rust-lang/crates.io-index" 602 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 603 604 + [[package]] 605 + name = "smoltcp" 606 + version = "0.12.0" 607 + source = "registry+https://github.com/rust-lang/crates.io-index" 608 + checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" 609 + dependencies = [ 610 + "bitflags 1.3.2", 611 + "byteorder", 612 + "cfg-if", 613 + "heapless 0.8.0", 614 + "managed", 615 + ] 616 + 617 [[package]] 618 name = "spidev" 619 version = "0.6.1" ··· 686 source = "registry+https://github.com/rust-lang/crates.io-index" 687 checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 688 689 + [[package]] 690 + name = "void" 691 + version = "1.0.2" 692 + source = "registry+https://github.com/rust-lang/crates.io-index" 693 + checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 694 + 695 [[package]] 696 name = "windows-sys" 697 version = "0.52.0"
+9 -2
Cargo.toml
··· 1 [workspace] 2 resolver = "3" 3 - members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-fnv", "sachy-shtc3"] 4 5 [workspace.package] 6 authors = ["Sachymetsu <sachymetsu@tutamail.com>"] ··· 8 repository = "https://tangled.org/sachy.dev/sachy-embed-core/" 9 license = "MIT OR Apache-2.0" 10 version = "0.1.0" 11 - rust-version = "1.88.0"
··· 1 [workspace] 2 resolver = "3" 3 + members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-fnv", "sachy-shtc3", "sachy-sntp"] 4 5 [workspace.package] 6 authors = ["Sachymetsu <sachymetsu@tutamail.com>"] ··· 8 repository = "https://tangled.org/sachy.dev/sachy-embed-core/" 9 license = "MIT OR Apache-2.0" 10 version = "0.1.0" 11 + rust-version = "1.89.0" 12 + 13 + [workspace.dependencies] 14 + embassy-futures = { version = "0.1" } 15 + embassy-time = { version = "0.5" } 16 + embassy-sync = { version = "0.7" } 17 + embassy-net = { version = "0.7" } 18 + defmt = { version = "1" }
+1 -1
sachy-fmt/Cargo.toml
··· 9 rust-version = { workspace = true } 10 11 [dependencies] 12 - defmt = { version = "1", optional = true } 13 14 [features] 15 defmt = ["dep:defmt"]
··· 9 rust-version = { workspace = true } 10 11 [dependencies] 12 + defmt = { workspace = true, optional = true } 13 14 [features] 15 defmt = ["dep:defmt"]
+26
sachy-sntp/Cargo.toml
···
··· 1 + [package] 2 + name = "sachy-sntp" 3 + authors.workspace = true 4 + edition.workspace = true 5 + repository.workspace = true 6 + license.workspace = true 7 + version.workspace = true 8 + rust-version.workspace = true 9 + 10 + [dependencies] 11 + chrono = { version = "0.4.41", default-features = false, optional = true } 12 + defmt = { workspace = true, optional = true } 13 + embassy-net = { workspace = true, features = [ 14 + "udp", 15 + "proto-ipv4", 16 + "medium-ip", 17 + "medium-ethernet", 18 + ], optional = true } 19 + embassy-time = { workspace = true, optional = true } 20 + sachy-fmt = { path = "../sachy-fmt" } 21 + 22 + [features] 23 + default = ["chrono", "embassy-net"] 24 + chrono = ["dep:chrono"] 25 + embassy-net = ["dep:embassy-net", "dep:embassy-time"] 26 + defmt = ["dep:defmt"]
+209
sachy-sntp/src/lib.rs
···
··· 1 + #![no_std] 2 + 3 + #[derive(Debug, PartialEq, Eq)] 4 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 5 + pub enum SntpError { 6 + InvalidPacket, 7 + InvalidVersion, 8 + InvalidMode, 9 + KissOfDeath, 10 + InvalidTime, 11 + NetFailure, 12 + NetTimeout, 13 + } 14 + 15 + pub struct SntpRequest; 16 + 17 + #[derive(Debug, PartialEq, Eq, Clone, Copy)] 18 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 19 + /// SNTP timestamp. 20 + pub struct SntpTimestamp(u64); 21 + 22 + impl SntpTimestamp { 23 + pub fn microseconds(&self) -> u64 { 24 + (self.0 >> 32) * 1_000_000 + ((self.0 & 0xFFFFFFFF) * 1_000_000 / 0x100000000) 25 + } 26 + 27 + /// Returns true if the most significant bit is set. 28 + /// 29 + /// Relevant documentation from RFC 2030: 30 + /// 31 + /// ```text 32 + /// Note that, since some time in 1968 (second 2,147,483,648) the most 33 + /// significant bit (bit 0 of the integer part) has been set and that the 34 + /// 64-bit field will overflow some time in 2036 (second 4,294,967,296). 35 + /// Should NTP or SNTP be in use in 2036, some external means will be 36 + /// necessary to qualify time relative to 1900 and time relative to 2036 37 + /// (and other multiples of 136 years). There will exist a 200-picosecond 38 + /// interval, henceforth ignored, every 136 years when the 64-bit field 39 + /// will be 0, which by convention is interpreted as an invalid or 40 + /// unavailable timestamp. 41 + /// As the NTP timestamp format has been in use for the last 17 years, 42 + /// it remains a possibility that it will be in use 40 years from now 43 + /// when the seconds field overflows. As it is probably inappropriate 44 + /// to archive NTP timestamps before bit 0 was set in 1968, a 45 + /// convenient way to extend the useful life of NTP timestamps is the 46 + /// following convention: If bit 0 is set, the UTC time is in the 47 + /// range 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC on 1 48 + /// January 1900. If bit 0 is not set, the time is in the range 2036- 49 + /// 2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February 50 + /// 2036. Note that when calculating the correspondence, 2000 is not a 51 + /// leap year. Note also that leap seconds are not counted in the 52 + /// reckoning. 53 + ///``` 54 + pub fn msb_set(&self) -> bool { 55 + self.0 & (1 << 63) != 0 56 + } 57 + 58 + /// Microseconds since the UNIX epoch. 59 + pub fn utc_micros(&self) -> i64 { 60 + let ntp_epoch_micros = self.microseconds() as i64; 61 + let offset: i64 = if self.msb_set() { 62 + -2208988800000000 63 + } else { 64 + 2085978496000000 65 + }; 66 + 67 + ntp_epoch_micros + offset 68 + } 69 + 70 + #[cfg(feature = "chrono")] 71 + pub fn try_to_naive_datetime(self) -> Result<chrono::NaiveDateTime, SntpError> { 72 + self.try_into() 73 + } 74 + } 75 + 76 + impl SntpRequest { 77 + pub const SNTP_PACKET_SIZE: usize = 48; 78 + 79 + pub const fn create_packet() -> [u8; Self::SNTP_PACKET_SIZE] { 80 + let mut packet = [0u8; Self::SNTP_PACKET_SIZE]; 81 + packet[0] = (3 << 6) | (4 << 3) | 3; 82 + packet 83 + } 84 + 85 + pub fn create_packet_from_buffer(packet: &mut [u8]) -> Result<(), SntpError> { 86 + if packet.len() != Self::SNTP_PACKET_SIZE { 87 + return Err(SntpError::InvalidPacket); 88 + } 89 + 90 + packet[0] = (3 << 6) | (4 << 3) | 3; 91 + 92 + Ok(()) 93 + } 94 + 95 + pub fn read_timestamp(packet: &[u8]) -> Result<SntpTimestamp, SntpError> { 96 + if packet.len() == Self::SNTP_PACKET_SIZE { 97 + let header = packet[0]; 98 + let version = (header & 0x38) >> 3; 99 + 100 + if version != 4 { 101 + return Err(SntpError::InvalidVersion); 102 + } 103 + 104 + let mode = header & 0x7; 105 + 106 + if !(4..=5).contains(&mode) { 107 + return Err(SntpError::InvalidMode); 108 + } 109 + 110 + let kiss_of_death = packet[1] == 0; 111 + 112 + if kiss_of_death { 113 + return Err(SntpError::KissOfDeath); 114 + } 115 + 116 + let timestamp = SntpTimestamp(read_be_u64(&packet[40..48])); 117 + 118 + return Ok(timestamp); 119 + } 120 + 121 + Err(SntpError::InvalidPacket) 122 + } 123 + 124 + #[cfg(feature = "chrono")] 125 + pub fn as_naive_datetime(raw_time: SntpTimestamp) -> Result<chrono::NaiveDateTime, SntpError> { 126 + raw_time.try_into() 127 + } 128 + } 129 + 130 + #[cfg(feature = "chrono")] 131 + impl TryFrom<SntpTimestamp> for chrono::NaiveDateTime { 132 + type Error = SntpError; 133 + 134 + fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> { 135 + Ok(chrono::DateTime::<chrono::Utc>::try_from(timestamp)?.naive_utc()) 136 + } 137 + } 138 + 139 + #[cfg(feature = "chrono")] 140 + impl TryFrom<SntpTimestamp> for chrono::DateTime<chrono::Utc> { 141 + type Error = SntpError; 142 + 143 + fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> { 144 + chrono::DateTime::<chrono::Utc>::from_timestamp_micros(timestamp.utc_micros()) 145 + .ok_or(SntpError::InvalidTime) 146 + } 147 + } 148 + 149 + #[inline] 150 + fn read_be_u64(input: &[u8]) -> u64 { 151 + let (int_bytes, _) = input.split_at(core::mem::size_of::<u64>()); 152 + u64::from_be_bytes(int_bytes.try_into().unwrap()) 153 + } 154 + 155 + #[cfg(feature = "embassy-net")] 156 + pub trait SntpSocket { 157 + fn resolve_time( 158 + &mut self, 159 + addrs: &[embassy_net::IpAddress], 160 + ) -> impl Future<Output = Result<SntpTimestamp, SntpError>>; 161 + } 162 + 163 + #[cfg(feature = "embassy-net")] 164 + impl SntpSocket for embassy_net::udp::UdpSocket<'_> { 165 + async fn resolve_time( 166 + &mut self, 167 + addrs: &[embassy_net::IpAddress], 168 + ) -> Result<SntpTimestamp, SntpError> { 169 + use embassy_time::{Duration, WithTimeout}; 170 + use sachy_fmt::{debug, error}; 171 + 172 + if addrs.is_empty() { 173 + return Err(SntpError::NetFailure); 174 + } 175 + 176 + debug!("SNTP address list: {}", addrs); 177 + 178 + let addr = addrs[0]; 179 + 180 + debug!("Binding to port 123"); 181 + self.bind(123).map_err(|_| SntpError::NetFailure)?; 182 + 183 + debug!("Sending SNTP request"); 184 + self.send_to_with( 185 + SntpRequest::SNTP_PACKET_SIZE, 186 + embassy_net::IpEndpoint::new(addr, 123), 187 + SntpRequest::create_packet_from_buffer, 188 + ) 189 + .await 190 + .map_err(|e| { 191 + error!("Failed to send: {}", e); 192 + SntpError::NetFailure 193 + })??; 194 + 195 + debug!("Waiting for a response..."); 196 + let res = self 197 + .recv_from_with(|buf, _from| SntpRequest::read_timestamp(buf)) 198 + .with_timeout(Duration::from_secs(5)) 199 + .await 200 + .map_err(|_| SntpError::NetTimeout) 201 + .flatten(); 202 + 203 + debug!("Received: {}", &res); 204 + debug!("Closing SNTP port..."); 205 + self.close(); 206 + 207 + res 208 + } 209 + }