178 Commits

Author SHA1 Message Date
256407432d create release checklist
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 13:06:22 +01:00
c59b015a20 added additional tests
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 12:53:39 +01:00
e081504b33 Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/spacepackets
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-22 12:51:23 +01:00
6eb1b1efbc bugfix, additional test and CHANGELOG bump 2023-01-22 12:50:49 +01:00
51d0a08e7b better document panics and tweak Add/AddAssign impl
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
for UnixTimestamp
2023-01-22 01:54:41 +01:00
6e557c2568 add Add impls for &
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 14:50:35 +01:00
fc76a975c1 add Add<Duration> for &UnixTimestamp
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 14:28:29 +01:00
74e489bd07 add basic tests and update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 13:17:38 +01:00
1e90793072 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-21 11:37:57 +01:00
ef55bc4a6e Improve time module
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-21 01:25:05 +01:00
55862a2433 docs typo
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-20 20:01:27 +01:00
9afc3fc8de Update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-20 19:48:20 +01:00
368331cc60 some more doc fixes
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-20 19:46:26 +01:00
416b5c1805 Some doc improvements 2023-01-20 19:42:28 +01:00
fa53be0934 bump version to v0.5.0 2023-01-20 19:08:12 +01:00
65440ada35 Merge pull request 'add first Add and AddAssign impl for CUC' (#11) from cuc_time_extensions into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #11
2023-01-18 21:53:13 +01:00
80e1be676e that should do it
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-17 01:16:07 +01:00
419d3e2c56 Merge remote-tracking branch 'origin/main' into cuc_time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-17 00:06:56 +01:00
37af989a03 Merge remote-tracking branch 'origin/main' into time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-17 00:05:23 +01:00
904354abc0 last bugfix
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-17 00:04:31 +01:00
264b1a514a more bugs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 23:27:42 +01:00
ab1179ed5f Merge pull request 'ECSS TM timestamp updates' (#10) from ecss_tm_timestamp_updates into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #10
2023-01-16 18:39:56 +01:00
1300923273 Merge branch 'main' into cuc_time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-16 18:01:03 +01:00
030c8daf45 bump changelog again
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 18:00:23 +01:00
50edd8b3b4 Merge remote-tracking branch 'origin/main' into ecss_tm_timestamp_updates
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-16 17:57:19 +01:00
6e7eabe18f Merge pull request 'Time module extensions' (#9) from time_extensions into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #9
2023-01-16 17:51:27 +01:00
9fb445d89a Merge remote-tracking branch 'origin/main' into time_extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-16 16:54:49 +01:00
e545cf11ee bump version
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2023-01-16 11:57:30 +01:00
00f399c23a timestamp is optional now
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 11:56:25 +01:00
1652567b8c add first Add and AddAssign impl for CUC
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-16 00:47:24 +01:00
14a971f01c remove unnecessary docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-15 21:54:56 +01:00
e3f8b4a23b re-order changelog
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 21:53:25 +01:00
c8d442690b changelog should be complete
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 21:47:35 +01:00
708b68a5cb use new struct in API 2023-01-15 21:30:31 +01:00
2f51420a29 consistency renaming 2023-01-15 21:13:48 +01:00
973c54e0de extend changelog 2023-01-15 21:12:54 +01:00
6f795690fd start updating changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-15 20:48:39 +01:00
49f3497ca8 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 20:29:58 +01:00
f1f9f695e5 put include behind feature gate
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 20:29:43 +01:00
ed850b1df4 use core include for AddAssign
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 20:28:35 +01:00
b2def8cd36 added one more test
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 20:26:26 +01:00
73dbc80cad small tweak
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 18:26:18 +01:00
51e134f031 bugfixes for precision handling for additions
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 18:24:49 +01:00
73575bd00f reaching target coverage
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 17:42:23 +01:00
4c280b22c8 hmm associated method is tricky
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 12:13:39 +01:00
405145496f typo fix
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 11:57:58 +01:00
22f3b72faf dyn provider works great
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 11:54:23 +01:00
39bf0c6a61 Add AddAssign impl and addition unittests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-15 02:37:56 +01:00
3f6c4c6f46 add dynamic cds time provider function 2023-01-15 01:33:26 +01:00
24e6e50e2f add some more helper methods, improve example
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 01:15:35 +01:00
ec452130f9 added missing feature gate
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-15 00:38:39 +01:00
67cf9ec0c0 added some more tests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:57:15 +01:00
8fddaefab1 Merge pull request 'Another CDS timestamp bugfix' (#8) from another_logic_fix_cds_stamp into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #8
2023-01-14 17:50:23 +01:00
a24c9fc042 Merge branch 'another_logic_fix_cds_stamp' into add_time_helpers 2023-01-14 17:50:10 +01:00
6e593e4e27 update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-14 17:47:52 +01:00
ef5a4e2924 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-14 17:46:58 +01:00
aa5c206c1c another small bugfix
Some checks are pending
Rust/spacepackets/pipeline/head Build queued...
2023-01-14 17:46:43 +01:00
c284a7a3cc and another dumb logic error for submillis ps prec
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:43:19 +01:00
5c6c016e13 Merge remote-tracking branch 'origin/main' into add_time_helpers 2023-01-14 17:29:56 +01:00
1c702f933f v0.4.1
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-14 17:22:27 +01:00
6989558f93 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-14 17:20:14 +01:00
33e9b40c39 Merge pull request 'bugfix for patch version' (#7) from bugfix_for_v0.4.1 into main
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Reviewed-on: #7
2023-01-14 17:19:22 +01:00
008359ec71 bugfix for patch version
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:18:02 +01:00
753be86272 added first tests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 17:15:48 +01:00
d071de3a86 almost complete
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 16:12:02 +01:00
40f64525b6 some smaller tweaks 2023-01-14 15:43:49 +01:00
8da93443b9 now to test it..
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-14 15:37:18 +01:00
3ba68b4e64 add new Conversion helper 2023-01-14 13:22:32 +01:00
b350f8fe03 remove lint allowance
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-13 23:25:09 +01:00
0a774afd41 added Add impl for both TimeProviders 2023-01-13 23:24:37 +01:00
1761bdd33f add first Add<Duration> impl for TimeProvider<DaysLen16Bits> 2023-01-13 23:04:53 +01:00
2385e7812b update dockerfile, install nightly
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-11 00:19:05 +01:00
76ea418711 add docs step in jenkinsfile
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-11 00:16:09 +01:00
b68f1c3752 add doc check in CI
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-11 00:14:23 +01:00
7c9bdb4512 add github msrv check
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-11 00:02:37 +01:00
8ffa7efa4d update jenkins CI
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-10 23:22:09 +01:00
d972dd5223 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-10 16:59:13 +01:00
481de83fdb move lifetime docs further up
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-10 16:20:50 +01:00
09b305f529 better names for lifetimes
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-10 16:19:21 +01:00
78c5787e07 add some more basic docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-10 16:12:20 +01:00
e9e33b0335 release this tomorrow
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-10 00:00:10 +01:00
455be77f4a more docs 2023-01-09 23:59:55 +01:00
c748657499 allow minor release bump for serde and crc dependency
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 23:49:17 +01:00
2e90cba5bd put some ASCII includes behind alloc feature
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 23:47:47 +01:00
f290d2a54e re-remove Default impl for CDS time provider
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 23:46:31 +01:00
da695e4705 v0.4.0 preparation
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 11:21:07 +01:00
5c222735d4 Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/spacepackets
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 11:19:16 +01:00
1432c298b3 feature list update 2023-01-09 11:19:10 +01:00
1b45082ace Merge pull request 'Add CUC impl' (#4) from add_cuc_time_impl into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #4
Reviewed-by: Paul Nehlich <nehlichp@irs.uni-stuttgart.de>
2023-01-09 11:14:40 +01:00
4c20158dcc Merge branch 'main' into add_cuc_time_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-09 11:12:47 +01:00
c879181093 update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 11:12:07 +01:00
6c88e94742 Merge remote-tracking branch 'origin/main' into add_cuc_time_impl
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2023-01-09 11:10:44 +01:00
3fb2fbd20c Merge pull request 'Improve CDS timecode implementation' (#3) from improve_cds_short_impl into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #3
2023-01-09 11:08:11 +01:00
ec8a2e1d24 rename pfield preamble constant, add for CUC
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2023-01-09 11:07:43 +01:00
192e2f2c76 make pfield public
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-09 11:06:51 +01:00
5df221759f conversion from now bugfix
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-22 23:55:05 +01:00
f137bd2549 improve tests
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-22 23:45:15 +01:00
630bffec51 Merge branch 'main' into improve_cds_short_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-22 23:12:35 +01:00
a14ae37cac Merge pull request 'added sp header getter function' (#6) from sp_header_getter into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #6
2022-12-22 09:06:33 +01:00
2758699601 added sp header getter function
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-21 22:09:45 +01:00
b07cec28ea update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-21 10:14:58 +01:00
51963d0f72 remove default impl for cds TimeProvider 2022-12-21 10:14:13 +01:00
83e2cad753 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-21 09:47:09 +01:00
472bfa9964 add floating point division code
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-21 01:17:36 +01:00
14fa1bad92 add TODO
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-21 01:03:26 +01:00
3828a98c76 important bugfix
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-21 01:01:05 +01:00
f641248ac2 add PR link
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-20 16:28:10 +01:00
884de647ad add PR link
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-20 16:27:23 +01:00
0d8074c6b9 clippy fixes
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-20 16:26:04 +01:00
6798e3a6f5 Merge remote-tracking branch 'origin/main' into improve_cds_short_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-20 16:23:24 +01:00
7e763fe055 Merge remote-tracking branch 'origin/main' into add_cuc_time_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-20 16:22:23 +01:00
4410ee7eec bump changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-20 16:21:30 +01:00
2895d7645a Merge pull request 'PTC and PFC extensions' (#5) from ptc_pfc_extension into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #5
2022-12-20 16:19:21 +01:00
692d12e5a5 Merge branch 'main' into ptc_pfc_extension
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-20 16:15:46 +01:00
9e57ce3872 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-19 17:02:19 +01:00
fd13694904 add PUS service ID enum
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-19 16:36:18 +01:00
976fe9c49b README updates
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-19 11:02:36 +01:00
fbeea41e8f fix in Cargo.toml file 2022-12-19 11:00:45 +01:00
177ddba9c5 clippy fixes
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2022-12-19 00:01:07 +01:00
f964342556 removed unnecessary casts
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-18 23:54:13 +01:00
46e2af41d2 PTC and PFC extensions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
- Add Ptc typedefinition for PacketTypeCode enum
- Add `UnsignedPfc` and `RealPfc` PFC enumerations
2022-12-18 16:21:20 +01:00
fc05eaa925 Merge remote-tracking branch 'origin/main' into add_cuc_time_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-12 11:57:00 +01:00
5a878ef6a3 Merge remote-tracking branch 'origin/main' into improve_cds_short_impl
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2022-12-12 11:56:08 +01:00
66d77fda36 Merge pull request 'Add Error impls if std feature is used' (#2) from add_error_impls_for_std_feature into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #2
2022-12-12 11:54:51 +01:00
ef963187ac update changelog
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2022-12-10 18:27:13 +01:00
93159dae45 some more docs
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-10 18:23:47 +01:00
f73edd71fd better structure
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-10 18:18:41 +01:00
6341cf35d3 added doc test / example as well
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-10 18:09:00 +01:00
1d9329ad63 this should cover most basic cases
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-10 17:39:15 +01:00
d889826b79 remove duplicate function
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-10 16:42:24 +01:00
e155ddbcb0 add more tests and additional check
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-10 16:35:00 +01:00
7615e40e43 basic docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-09 17:33:04 +01:00
bccbdf65a3 basic impl done, add first unittests
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-09 17:21:45 +01:00
8d0de0dce4 continued cuc impl 2022-12-09 16:51:48 +01:00
5958d19eb4 continue cuc impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-09 13:50:04 +01:00
4d64394637 split up large source file
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-08 15:22:19 +01:00
c4f8eee8da base line cuc impl done 2022-12-08 14:59:12 +01:00
f7f500ea48 Merge branch 'improve_cds_short_impl' into add_cuc_time_impl 2022-12-08 13:04:44 +01:00
7f607be5f3 add some missing derives
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2022-12-08 12:56:34 +01:00
c60ebc2a6e start baseline CUC Impl 2022-12-08 00:29:07 +01:00
48e5b22c27 doc corrections 2022-12-07 22:54:03 +01:00
04073a29af Merge remote-tracking branch 'origin/main' into improve_cds_short_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-07 19:06:57 +01:00
66b4bf6013 Merge remote-tracking branch 'origin/main' into add_error_impls_for_std_feature
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-07 19:05:55 +01:00
f5397b8352 add Rust MSRV 1.60
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-07 19:05:09 +01:00
180134c05d update README and lib.rs frontpage docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-07 18:49:19 +01:00
3ee237865c Merge remote-tracking branch 'origin/main' into improve_cds_short_impl
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-07 18:44:06 +01:00
afd59b14fc Merge remote-tracking branch 'origin/main' into add_error_impls_for_std_feature
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2022-12-07 18:43:56 +01:00
3d5829b9a8 add missing feature flag
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-07 18:41:45 +01:00
1b4934e865 Merge branch 'add_error_impls_for_std_feature' into improve_cds_short_impl
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-12-07 14:43:03 +01:00
3fa43d46f9 bump changelog
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-12-07 14:42:53 +01:00
4cd8f143df this is a lot better 2022-12-07 14:42:01 +01:00
878f072e9a extended CDS impl 2022-12-07 13:55:43 +01:00
25695b39ea add error impls for feature std 2022-12-07 08:14:55 +01:00
8c0b78c698 ascii timecodes complete
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-12-05 10:14:25 +01:00
4e1df03729 bump changelog
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-12-05 10:02:50 +01:00
f13a2f73eb this should suffice
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-12-05 10:01:45 +01:00
62c1462930 added ASCII time code module
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2022-12-05 09:48:11 +01:00
8358d5ed1c bump changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-05 09:05:35 +01:00
0c77a04059 doc tweaks
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-05 09:03:40 +01:00
e8571866e8 doc fix
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-05 08:55:24 +01:00
038755e56e set minimal data len in example
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-05 01:44:53 +01:00
33d78292c6 better example 2022-12-05 01:44:28 +01:00
8cf635d2fa additional useful functions 2022-12-05 01:41:35 +01:00
3420bcbeba Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/spacepackets
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-05 00:18:16 +01:00
6554241ed7 doc correction 2022-12-05 00:17:37 +01:00
283f9ff495 Merge pull request 'Various improvements' (#1) from various_improvements into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #1
2022-12-05 00:16:07 +01:00
3a71b00198 cargo fmt
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 20:00:49 +01:00
13be7ca1e7 do not push version just yet
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-04 19:58:53 +01:00
098a534199 bump changelog
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 19:57:51 +01:00
322a56335e add tests for new functionality
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 19:56:21 +01:00
f7c688d8db line break
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 18:26:38 +01:00
54bb4bdaaa new helper functions for CCSDS SP construction
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 18:25:30 +01:00
1969a26f14 bump changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-04 17:22:51 +01:00
d28ea7d4da add docs for new feature
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2022-12-04 17:18:10 +01:00
aeb2e806b8 move improvements
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 17:11:44 +01:00
938c4ba770 make serde dependency optional
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-04 12:17:36 +01:00
97c70bf03b bump changelog and cargo.toml
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-03 15:47:18 +01:00
dc6d726e61 added missing doc_cfg feature for doc_cfg attr
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2022-12-03 15:40:36 +01:00
16 changed files with 4899 additions and 701 deletions

View File

@ -20,6 +20,21 @@ jobs:
command: check
args: --release
msrv:
name: Check with MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.60.0
override: true
profile: minimal
- uses: actions-rs/cargo@v1
with:
command: check
args: --release
cross-check:
name: Check Cross
runs-on: ubuntu-latest
@ -58,6 +73,21 @@ jobs:
command: fmt
args: --all -- --check
check-doc:
name: Check Documentation Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
profile: minimal
- uses: actions-rs/cargo@v1
with:
command: doc
args: --all-features
clippy:
name: Clippy
runs-on: ubuntu-latest

View File

@ -8,6 +8,146 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.5.1] 2023-01-22
## Added
- `time::cds::TimeProvider`
- Add `Ord` and `PartialOrd`, use custom `PartialEq` impl to account for precision correctly.
- Add `precision_as_ns` function which converts microsecond and picosecond precision values
into nanoseconds.
- Add conversion trait to convert `cds::TimeProvider<DaysLen16Bits>` into
`cds::TimeProvider<DaysLen24Bits>` and vice-versa.
- `time::UnixTimestamp`
- Add `Ord` and `PartialOrd` implementations.
- Add `Add<Duration>` and `AddAssign<Duration>` implementations.
## Fixed
- `time::cds::TimeProvider`: Fixed a big where subsecond milliseconds were not accounted for
when the provider has no submillisecond precision.
# [v0.5.0] 2023-01-20
The timestamp of `PusTm` is now optional. See Added and Changed section for details.
## Added
- `PusTmSecondaryHeader`: New `new_simple_no_timestamp` API to create secondary header without
timestamp.
- `PusTm`: Add `new_simple_no_timestamp` method to create TM without timestamp
- New `UnixTimestamp` abstraction which contains the unix seconds as an `i64`
and an optional subsecond millisecond counter (`u16`)
- `MS_PER_DAY` constant.
- CUC: Added `from_date_time` and `from_unix_stamp` constructors for time provider.
- CUC: Add `Add<Duration>` and `AddAssign<Duration>` impl for time provider.
### CDS time module
- Implement `Add<Duration>` and `AddAssign<Duration>` for time providers, which allows
easily adding offsets to the providers.
- Implement `TryFrom<DateTime<Utc>>` for time providers.
- `get_dyn_time_provider_from_bytes`: Requires `alloc` support and returns
the correct `TimeProvider` instance wrapped as a boxed trait object
`Box<DynCdsTimeProvider>` by checking the length of days field.
- Added constructor function to create the time provider
from `chrono::DateTime<Utc>` and a generic UNIX timestamp (`i64` seconds
and subsecond milliseconds).
- `MAX_DAYS_24_BITS` which contains maximum value which can be supplied
to the days field of a CDS time provider with 24 bits days field width.
- New `CdsTimestamp` trait which encapsulates common fields for all CDS time providers.
- `from_unix_secs_with_u24_days` and `from_unix_secs_with_u16_days` which create
the time provider from a `UnixTimestamp` reference.
- `from_dt_with_u16_days`, `from_dt_with_u24_days` and their `..._us_precision` and
`..._ps_precision` variants which allow to create time providers from
a `chrono::DateTime<Utc>`.
- Add `from_bytes_with_u24_days` and `from_bytes_with_u16_days` associated methods
## Changed
- (breaking) `unix_epoch_to_ccsds_epoch`: Expect and return `i64` instead of `u64` now.
- (breaking) `ccsds_epoch_to_unix_epoch`: Expect and return `i64` instead of `u64` now.
- (breaking) `PusTmSecondaryHeader`: Timestamp is optional now, which translates to a
timestamp of size 0.
- (breaking): `PusTm`: Renamed `time_stamp` method to `timestamp`, also returns
`Optional<&'src_data [u8]>` now.
- (breaking): `PusTmSecondaryHeader`: Renamed `time_stamp` field to `timestamp` for consistency.
- (breaking): Renamed `from_now_with_u24_days_and_us_prec` to `from_now_with_u24_days_us_precision`.
Also did the same for the `u16` variant.
- (breaking): Renamed `from_now_with_u24_days_and_ps_prec` to `from_now_with_u24_days_ps_precision`.
Also did the same for the `u16` variant.
- `CcsdsTimeProvider` trait (breaking):
- Add new `unix_stamp` method returning the new `UnixTimeStamp` struct.
- Add new `subsecond_millis` method returning counter `Option<u16>`.
- Default impl for `unix_stamp` which re-uses `subsecond_millis` and
existing `unix_seconds` method.
- `TimestampError` (breaking): Add `DateBeforeCcsdsEpoch` error type
because new CDS API allow supplying invalid date times before CCSDS epoch.
Make `TimestampError` with `#[non_exhaustive]` attribute to prevent
future breakages if new error variants are added.
# [v0.4.2] 14.01.2023
## Fixed
- CDS timestamp: Fixed another small logic error for stamp creation from the current
time with picosecond precision.
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/8
# [v0.4.1] 14.01.2023
## Fixed
- CDS timestamp: The conversion function from the current time were buggy
when specifying picoseconds precision, which could lead to overflow
multiplications and/or incorrect precision fields.
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/7
# [v0.4.0] 10.01.2023
## Fixed
- Remove `Default` derive on CDS time provider. This can lead to uninitialized preamble fields.
## Changed
- `serde` support is now optional and behind the `serde` feature.
- `PusTcSecondaryHeaderT` trait renamed to `GenericPusTcSecondaryHeader`.
- `PusTmSecondaryHeaderT` trait renamed to `GenericPusTmSecondaryHeader`.
- `SpHeader`: Former `tc` and `tm` methods now named `tc_unseg` and `tm_unseg`.
Former `new` method now called `new_from_single_fields`.
- `SpHeader`: Renamed `from_bytes` to `from_be_bytes`.
The function now returns the remaining slice as well.
- All CDS specific functionality was moved into the `cds` submodule of the `time`
module. `CdsShortTimeProvider` was renamed to `TimeProvider`.
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/3
## Added
- `SpHeader` getter function `sp_header` added for `PusTc`
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/6
- Added PFC enumerations: `ecss::UnsignedPfc` and `ecss::RealPfc`.
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/5
- Added `std::error::Error` implementation for all error enumerations if the `std` feature
is enabled.
- CUC timestamp implementation as specified in CCSDS 301.0-B-4 section 3.2.
PR: https://egit.irs.uni-stuttgart.de/rust/spacepackets/pulls/4/files
- ACII timestamps as specified in CCSDS 301.0-B-4 section 3.5.
- Added MSRV in `Cargo.toml` with the `rust-version` field set to Rust 1.60.
- `serde` `Serialize` and `Deserialize` added to all types.
- Added `const` constructors for `PacketId`, `PacketSeqCtrl` and
`SpHeader`.
- Added `PartialEq` and `Eq` `derive`s to `TimeProvider`.
- `SpHeader`: Added serialization function into raw format `write_to_be_bytes`.
- Added 24-bit day field support for CDS short. The bit width is configured at type level
via a generic parameter type passed to the `cds::TimeProvider`
- Added submillisecond precision support for `cds::TimeProvider`
# [v0.3.1] 03.12.2022
- Small fix for faulty docs.rs build
# [v0.3.0] 01.12.2022
## Added

View File

@ -1,7 +1,8 @@
[package]
name = "spacepackets"
version = "0.3.0"
version = "0.5.1"
edition = "2021"
rust-version = "1.60"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Generic implementations for various CCSDS and ECSS packet standards"
homepage = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
@ -13,11 +14,12 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
[dependencies]
zerocopy = "0.6"
crc = "3.0"
crc = "3"
delegate = "0.8"
[dependencies.serde]
version = "1.0"
version = "1"
optional = true
default-features = false
features = ["derive"]
@ -35,7 +37,8 @@ version = "1.0"
[features]
default = ["std"]
std = ["chrono/std", "chrono/clock", "alloc"]
alloc = ["postcard/alloc"]
serde = ["dep:serde", "chrono/serde"]
alloc = ["postcard/alloc", "chrono/alloc"]
[package.metadata.docs.rs]
all-features = true

View File

@ -15,25 +15,32 @@ Currently, this includes the following components:
[CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf)
- PUS Telecommand and PUS Telemetry implementation according to the
[ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
- CDS Short Time Code implementation according to
[CCSDS CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
- CUC (CCSDS Unsegmented Time Code) implementation according to
[CCSDS 301.0-B-4 3.2](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
- CDS (CCSDS Day Segmented Time Code) implementation according to
[CCSDS 301.0-B-4 3.3](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
- Some helper types to support ASCII timecodes as specified in
[CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
# Features
`spacepackets` supports various runtime environments and is also suitable for `no_std` environments.
It offers support for [`serde`](https://serde.rs/). The Space Packet, PUS TM and TC
implementations derive the `serde` `Serialize` and `Deserialize` trait. This allows serializing and
It also offers optional support for [`serde`](https://serde.rs/). This allows serializing and
deserializing them with an appropriate `serde` provider like
[`postcard`](https://github.com/jamesmunns/postcard).
Default features:
## Default features
- [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
- [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
Enabled by the `std` feature.
## Optional Features
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
# Examples
You can check the [documentation](https://docs.rs/spacepackets) of individual modules for various

View File

@ -10,6 +10,6 @@ ARG DEBIAN_FRONTEND=noninteractive
# set CROSS_CONTAINER_IN_CONTAINER to inform `cross` that it is executed from within a container
ENV CROSS_CONTAINER_IN_CONTAINER=true
# TODO: installing cross is problematic, permission issues
RUN rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
RUN rustup install nightly && \
rustup target add thumbv7em-none-eabihf armv7-unknown-linux-gnueabihf && \
rustup component add rustfmt clippy

View File

@ -13,19 +13,29 @@ pipeline {
sh 'cargo clippy'
}
}
stage('Docs') {
steps {
sh 'cargo +nightly doc --all-features'
}
}
stage('Rustfmt') {
steps {
sh 'cargo fmt'
sh 'cargo fmt --all --check'
}
}
stage('Test') {
steps {
sh 'cargo test'
sh 'cargo test --all-features'
}
}
stage('Check') {
stage('Check with all features') {
steps {
sh 'cargo check'
sh 'cargo check --all-features'
}
}
stage('Check with no features') {
steps {
sh 'cargo check --no-default-features'
}
}
stage('Check Cross Embedded Bare Metal') {

18
release_checklist.md Normal file
View File

@ -0,0 +1,18 @@
Checklist for new releases
=======
# Pre-Release
1. Make sure any new modules are documented sufficiently enough and check docs with
`cargo doc --all-features --open`.
2. Bump version specifier in `Cargo.toml`.
3. Update `CHANGELOG.md`: Convert `unreleased` section into version section with date and add new
`unreleased` section.
4. Run `cargo test --all-features`.
5. Run `cargo fmt` and `cargo clippy`. Check `cargo msrv` against MSRV in `Cargo.toml`.
6. Wait for CI/CD results for EGit and Github. These also check cross-compilation for bare-metal
targets.
# Post-Release
1. Create a new release on `EGit` based on the release branch.

View File

@ -1,10 +1,13 @@
//! Common definitions and helpers required to create PUS TMTC packets according to
//! [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/)
use crate::{ByteConversionError, CcsdsPacket, SizeMissmatch};
use core::fmt::Debug;
use core::fmt::{Debug, Display, Formatter};
use core::mem::size_of;
use crc::{Crc, CRC_16_IBM_3740};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub type CrcType = u16;
@ -12,8 +15,24 @@ pub type CrcType = u16;
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PusServiceId {
/// Service 1
Verification = 1,
/// Service 3
Housekeeping = 3,
/// Service 5
Event = 5,
/// Service 8
Action = 8,
/// Service 17
Test = 17,
}
/// All PUS versions. Only PUS C is supported by this library.
#[derive(PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Debug)]
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PusVersion {
EsaPus = 0,
PusA = 1,
@ -34,7 +53,9 @@ impl TryFrom<u8> for PusVersion {
}
}
/// ECSS Packet Type Codes (PTC)s.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PacketTypeCodes {
Boolean = 1,
Enumerated = 2,
@ -50,7 +71,40 @@ pub enum PacketTypeCodes {
Packet = 12,
}
pub type Ptc = PacketTypeCodes;
/// ECSS Packet Field Codes (PFC)s for the unsigned [Ptc].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UnsignedPfc {
OneByte = 4,
TwelveBits = 8,
TwoBytes = 12,
ThreeBytes = 13,
FourBytes = 14,
SixBytes = 15,
EightBytes = 16,
OneBit = 17,
TwoBits = 18,
ThreeBits = 19,
}
/// ECSS Packet Field Codes (PFC)s for the real (floating point) [Ptc].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RealPfc {
/// 4 octets simple precision format (IEEE)
Float = 1,
/// 8 octets simple precision format (IEEE)
Double = 2,
/// 4 octets simple precision format (MIL-STD)
FloatMilStd = 3,
/// 8 octets simple precision format (MIL-STD)
DoubleMilStd = 4,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PusError {
VersionNotSupported(PusVersion),
IncorrectCrc(u16),
@ -61,12 +115,53 @@ pub enum PusError {
ByteConversionError(ByteConversionError),
}
impl Display for PusError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
PusError::VersionNotSupported(v) => {
write!(f, "PUS version {:?} not supported", v)
}
PusError::IncorrectCrc(crc) => {
write!(f, "crc16 {:#04x} is incorrect", crc)
}
PusError::RawDataTooShort(size) => {
write!(
f,
"deserialization error, provided raw data with size {} too short",
size
)
}
PusError::NoRawData => {
write!(f, "no raw data provided")
}
PusError::CrcCalculationMissing => {
write!(f, "crc16 was not calculated")
}
PusError::ByteConversionError(e) => {
write!(f, "low level byte conversion error: {}", e)
}
}
}
}
#[cfg(feature = "std")]
impl Error for PusError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let PusError::ByteConversionError(e) = self {
return Some(e);
}
None
}
}
impl From<ByteConversionError> for PusError {
fn from(e: ByteConversionError) -> Self {
PusError::ByteConversionError(e)
}
}
/// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry
/// (TM) packets. All PUS packets are also a special type of [CcsdsPacket]s.
pub trait PusPacket: CcsdsPacket {
const PUS_VERSION: PusVersion = PusVersion::PusC;
@ -179,6 +274,14 @@ pub trait ToBeBytes {
fn to_be_bytes(&self) -> Self::ByteArray;
}
impl ToBeBytes for () {
type ByteArray = [u8; 0];
fn to_be_bytes(&self) -> Self::ByteArray {
[]
}
}
impl ToBeBytes for u8 {
type ByteArray = [u8; 1];
@ -212,6 +315,7 @@ impl ToBeBytes for u64 {
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GenericEcssEnumWrapper<TYPE> {
val: TYPE,
}
@ -232,13 +336,13 @@ impl<TYPE: ToBeBytes> EcssEnumeration for GenericEcssEnumWrapper<TYPE> {
}
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<(), ByteConversionError> {
if buf.len() < self.byte_width() as usize {
if buf.len() < self.byte_width() {
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
found: buf.len(),
expected: self.byte_width() as usize,
expected: self.byte_width(),
}));
}
buf[0..self.byte_width() as usize].copy_from_slice(self.val.to_be_bytes().as_ref());
buf[0..self.byte_width()].copy_from_slice(self.val.to_be_bytes().as_ref());
Ok(())
}
}

View File

@ -9,47 +9,63 @@
//! [CCSDS Blue Book 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf)
//! - PUS Telecommand and PUS Telemetry implementation according to the
//! [ECSS-E-ST-70-41C standard](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/).
//! - CDS Short Time Code implementation according to
//! [CCSDS CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//! - CUC (CCSDS Unsegmented Time Code) implementation according to
//! [CCSDS 301.0-B-4 3.2](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//! - CDS (CCSDS Day Segmented Time Code) implementation according to
//! [CCSDS 301.0-B-4 3.3](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//! - Some helper types to support ASCII timecodes as specified in
//! [CCSDS 301.0-B-4 3.5](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
//!
//! ## Features
//!
//! `spacepackets` supports various runtime environments and is also suitable for `no_std` environments.
//!
//! It also offers support for [`serde`](https://serde.rs/). The Space Packet, PUS TM and TC
//! implementations derive the `serde` `Serialize` and `Deserialize` trait. This allows serializing and
//! It also offers optional support for [`serde`](https://serde.rs/). This allows serializing and
//! deserializing them with an appropriate `serde` provider like
//! [`postcard`](https://github.com/jamesmunns/postcard).
//!
//! Default features:
//! ### Default features
//!
//! - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
//! - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which operate on containers
//! like [`alloc::vec::Vec`](https://doc.rust-lang.org/beta/alloc/vec/struct.Vec.html).
//! Enabled by the `std` feature.
//!
//! ### Optional features
//!
//! - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and
//! `Deserialize` `derive`s
//!
//! ## Module
//!
//! This module contains helpers and data structures to generate Space Packets according to the
//! [CCSDS 133.0-B-2](https://public.ccsds.org/Pubs/133x0b2e1.pdf). This includes the
//! [SpHeader] class to generate the Space Packet Header component common to all space packets
//! [SpHeader] class to generate the Space Packet Header component common to all space packets.
//!
//! ## Example
//!
//! ```rust
//! use spacepackets::SpHeader;
//! let sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header");
//! let sp_header = SpHeader::tc_unseg(0x42, 12, 1).expect("Error creating CCSDS TC header");
//! println!("{:?}", sp_header);
//! let mut ccsds_buf: [u8; 32] = [0; 32];
//! sp_header.write_to_be_bytes(&mut ccsds_buf).expect("Writing CCSDS TC header failed");
//! println!("{:x?}", &ccsds_buf[0..6]);
//! ```
#![no_std]
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std;
use crate::ecss::CCSDS_HEADER_LEN;
use core::fmt::{Display, Formatter};
use delegate::delegate;
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub mod ecss;
@ -57,16 +73,22 @@ pub mod tc;
pub mod time;
pub mod tm;
mod private {
pub trait Sealed {}
}
pub const MAX_APID: u16 = 2u16.pow(11) - 1;
pub const MAX_SEQ_COUNT: u16 = 2u16.pow(14) - 1;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SizeMissmatch {
pub found: usize,
pub expected: usize,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ByteConversionError {
/// The passed slice is too small. Returns the passed slice length and expected minimum size
ToSliceTooSmall(SizeMissmatch),
@ -77,7 +99,39 @@ pub enum ByteConversionError {
ZeroCopyFromError,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
impl Display for ByteConversionError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
ByteConversionError::ToSliceTooSmall(missmatch) => {
write!(
f,
"target slice with size {} is too small, expected size of at least {}",
missmatch.found, missmatch.expected
)
}
ByteConversionError::FromSliceTooSmall(missmatch) => {
write!(
f,
"source slice with size {} too small, expected at least {} bytes",
missmatch.found, missmatch.expected
)
}
ByteConversionError::ZeroCopyToError => {
write!(f, "zerocopy serialization error")
}
ByteConversionError::ZeroCopyFromError => {
write!(f, "zerocopy deserialization error")
}
}
}
}
#[cfg(feature = "std")]
impl Error for ByteConversionError {}
/// CCSDS packet type enumeration.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PacketType {
Tm = 0,
Tc = 1,
@ -99,7 +153,8 @@ pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType {
PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap()
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SequenceFlags {
ContinuationSegment = 0b00,
FirstSegment = 0b01,
@ -123,21 +178,59 @@ impl TryFrom<u8> for SequenceFlags {
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
/// Abstraction for the CCSDS Packet ID, which forms the last thirteen bits
/// of the first two bytes in the CCSDS primary header.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PacketId {
pub ptype: PacketType,
pub sec_header_flag: bool,
apid: u16,
}
impl PacketId {
pub fn new(ptype: PacketType, sec_header_flag: bool, apid: u16) -> Option<PacketId> {
let mut pid = PacketId {
ptype,
sec_header_flag,
impl Default for PacketId {
fn default() -> Self {
PacketId {
ptype: PacketType::Tm,
sec_header_flag: false,
apid: 0,
};
pid.set_apid(apid).then_some(pid)
}
}
}
impl PacketId {
pub const fn const_tc(sec_header: bool, apid: u16) -> Self {
Self::const_new(PacketType::Tc, sec_header, apid)
}
pub const fn const_tm(sec_header: bool, apid: u16) -> Self {
Self::const_new(PacketType::Tm, sec_header, apid)
}
pub fn tc(sec_header: bool, apid: u16) -> Option<Self> {
Self::new(PacketType::Tc, sec_header, apid)
}
pub fn tm(sec_header: bool, apid: u16) -> Option<Self> {
Self::new(PacketType::Tm, sec_header, apid)
}
pub const fn const_new(ptype: PacketType, sec_header: bool, apid: u16) -> Self {
if apid > MAX_APID {
panic!("APID too large");
}
PacketId {
ptype,
sec_header_flag: sec_header,
apid,
}
}
pub fn new(ptype: PacketType, sec_header_flag: bool, apid: u16) -> Option<PacketId> {
if apid > MAX_APID {
return None;
}
Some(PacketId::const_new(ptype, sec_header_flag, apid))
}
/// Set a new Application Process ID (APID). If the passed number is invalid, the APID will
@ -170,27 +263,41 @@ impl From<u16> for PacketId {
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
/// Abstraction for the CCSDS Packet Sequence Control (PSC) field which is the
/// third and the fourth byte in the CCSDS primary header.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PacketSequenceCtrl {
pub seq_flags: SequenceFlags,
seq_count: u16,
}
impl PacketSequenceCtrl {
/// Returns [None] if the passed sequence count exceeds [MAX_SEQ_COUNT]
pub fn new(seq_flags: SequenceFlags, seq_count: u16) -> Option<PacketSequenceCtrl> {
let mut psc = PacketSequenceCtrl {
/// const variant of [PacketSequenceCtrl::new], but panics if the sequence count exceeds
/// [MAX_SEQ_COUNT].
const fn const_new(seq_flags: SequenceFlags, seq_count: u16) -> PacketSequenceCtrl {
if seq_count > MAX_SEQ_COUNT {
panic!("Sequence count too large");
}
PacketSequenceCtrl {
seq_flags,
seq_count: 0,
};
psc.set_seq_count(seq_count).then_some(psc)
seq_count,
}
}
/// Returns [None] if the passed sequence count exceeds [MAX_SEQ_COUNT].
pub fn new(seq_flags: SequenceFlags, seq_count: u16) -> Option<PacketSequenceCtrl> {
if seq_count > MAX_SEQ_COUNT {
return None;
}
Some(PacketSequenceCtrl::const_new(seq_flags, seq_count))
}
pub fn raw(&self) -> u16 {
((self.seq_flags as u16) << 14) | self.seq_count
}
/// Set a new sequence count. If the passed number is invalid, the sequence count will not be
/// set and false will be returned. The maximum allowed value for the 14-bit field is 16383
/// set and false will be returned. The maximum allowed value for the 14-bit field is 16383.
pub fn set_seq_count(&mut self, ssc: u16) -> bool {
if ssc > MAX_SEQ_COUNT {
return false;
@ -231,7 +338,7 @@ macro_rules! sph_from_other {
const SSC_MASK: u16 = 0x3FFF;
const VERSION_MASK: u16 = 0xE000;
/// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2
/// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2.
pub trait CcsdsPacket {
fn ccsds_version(&self) -> u8;
fn packet_id(&self) -> PacketId;
@ -245,7 +352,7 @@ pub trait CcsdsPacket {
}
/// Retrieve 13 bit Packet Identification field. Can usually be retrieved with a bitwise AND
/// of the first 2 bytes with 0x1FFF
/// of the first 2 bytes with 0x1FFF.
#[inline]
fn packet_id_raw(&self) -> u16 {
self.packet_id().raw()
@ -256,8 +363,8 @@ pub trait CcsdsPacket {
self.psc().raw()
}
/// Retrieve Packet Type (TM: 0, TC: 1).
#[inline]
/// Retrieve Packet Type (TM: 0, TC: 1)
fn ptype(&self) -> PacketType {
// This call should never fail because only 0 and 1 can be passed to the try_from call
self.packet_id().ptype
@ -274,13 +381,13 @@ pub trait CcsdsPacket {
}
/// Retrieve the secondary header flag. Returns true if a secondary header is present
/// and false if it is not
/// and false if it is not.
#[inline]
fn sec_header_flag(&self) -> bool {
self.packet_id().sec_header_flag
}
/// Retrieve Application Process ID
/// Retrieve Application Process ID.
#[inline]
fn apid(&self) -> u16 {
self.packet_id().apid
@ -308,16 +415,18 @@ pub trait CcsdsPrimaryHeader {
) -> Self;
}
/// Space Packet Primary Header according to CCSDS 133.0-B-2
/// Space Packet Primary Header according to CCSDS 133.0-B-2.
///
/// # Arguments
///
/// * `version` - CCSDS version field, occupies the first 3 bits of the raw header
/// * `version` - CCSDS version field, occupies the first 3 bits of the raw header. Will generally
/// be set to 0b000 in all constructors provided by this crate.
/// * `packet_id` - Packet Identifier, which can also be used as a start marker. Occupies the last
/// 13 bits of the first two bytes of the raw header
/// * `psc` - Packet Sequence Control, occupies the third and fourth byte of the raw header
/// * `data_len` - Data length field occupies the fifth and the sixth byte of the raw header
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpHeader {
pub version: u8,
pub packet_id: PacketId,
@ -326,63 +435,104 @@ pub struct SpHeader {
}
impl Default for SpHeader {
/// The default function sets the sequence flag field to [SequenceFlags::Unsegmented]. The data
/// length field is set to 1, which denotes an empty space packets.
fn default() -> Self {
SpHeader {
version: 0,
packet_id: PacketId {
ptype: PacketType::Tm,
apid: 0,
sec_header_flag: false,
},
packet_id: PacketId::default(),
psc: PacketSequenceCtrl {
seq_flags: SequenceFlags::Unsegmented,
seq_count: 0,
},
data_len: 0,
data_len: 1,
}
}
}
impl SpHeader {
/// Create a new Space Packet Header instance which can be used to create generic
/// Space Packets. This will return [None] if the APID or sequence count argument
/// exceed [MAX_APID] or [MAX_SEQ_COUNT] respectively.
pub fn new(
pub const fn new(packet_id: PacketId, psc: PacketSequenceCtrl, data_len: u16) -> Self {
Self {
version: 0,
packet_id,
psc,
data_len,
}
}
/// const variant of the [SpHeader::new_fron_single_fields] function. Panics if the passed
/// APID exceeds [MAX_APID] or the passed packet sequence count exceeds [MAX_SEQ_COUNT].
const fn const_new_from_single_fields(
ptype: PacketType,
sec_header: bool,
apid: u16,
seq_flags: SequenceFlags,
seq_count: u16,
data_len: u16,
) -> Self {
if seq_count > MAX_SEQ_COUNT {
panic!("Sequence count is too large");
}
if apid > MAX_APID {
panic!("APID is too large");
}
Self {
psc: PacketSequenceCtrl::const_new(seq_flags, seq_count),
packet_id: PacketId::const_new(ptype, sec_header, apid),
data_len,
version: 0,
}
}
/// Create a new Space Packet Header instance which can be used to create generic
/// Space Packets. This will return [None] if the APID or sequence count argument
/// exceed [MAX_APID] or [MAX_SEQ_COUNT] respectively. The version field is set to 0b000.
pub fn new_from_single_fields(
ptype: PacketType,
sec_header: bool,
apid: u16,
seq_flags: SequenceFlags,
seq_count: u16,
data_len: u16,
) -> Option<Self> {
if seq_count > MAX_SEQ_COUNT || apid > MAX_APID {
return None;
}
let mut header = SpHeader::default();
header.packet_id.sec_header_flag = sec_header;
header.packet_id.apid = apid;
header.packet_id.ptype = ptype;
header.psc.seq_count = seq_count;
header.data_len = data_len;
Some(header)
Some(SpHeader::const_new_from_single_fields(
ptype, sec_header, apid, seq_flags, seq_count, data_len,
))
}
/// Helper function for telemetry space packet headers. The packet type field will be
/// set accordingly.
pub fn tm(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new(PacketType::Tm, false, apid, seq_count, data_len)
/// Helper function for telemetry space packet headers. The packet type field will be
/// set accordingly. The secondary header flag field is set to false.
pub fn tm(apid: u16, seq_flags: SequenceFlags, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new_from_single_fields(PacketType::Tm, false, apid, seq_flags, seq_count, data_len)
}
/// Helper function for telecommand space packet headers. The packet type field will be
/// set accordingly.
pub fn tc(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new(PacketType::Tc, false, apid, seq_count, data_len)
/// Helper function for telemetry space packet headers. The packet type field will be
/// set accordingly. The secondary header flag field is set to false.
pub fn tc(apid: u16, seq_flags: SequenceFlags, seq_count: u16, data_len: u16) -> Option<Self> {
Self::new_from_single_fields(PacketType::Tc, false, apid, seq_flags, seq_count, data_len)
}
/// Variant of [SpHeader::tm] which sets the sequence flag field to [SequenceFlags::Unsegmented]
pub fn tm_unseg(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> {
Self::tm(apid, SequenceFlags::Unsegmented, seq_count, data_len)
}
/// Variant of [SpHeader::tc] which sets the sequence flag field to [SequenceFlags::Unsegmented]
pub fn tc_unseg(apid: u16, seq_count: u16, data_len: u16) -> Option<Self> {
Self::tc(apid, SequenceFlags::Unsegmented, seq_count, data_len)
}
//noinspection RsTraitImplementation
delegate!(to self.packet_id {
/// Returns [false] and fails if the APID exceeds [MAX_APID]
pub fn set_apid(&mut self, apid: u16) -> bool;
});
delegate!(to self.psc {
/// Returns [false] and fails if the sequence count exceeds [MAX_SEQ_COUNT]
pub fn set_seq_count(&mut self, seq_count: u16) -> bool;
});
@ -402,16 +552,38 @@ impl SpHeader {
self.packet_id.ptype = packet_type;
}
pub fn from_raw_slice(buf: &[u8]) -> Result<Self, ByteConversionError> {
if buf.len() < CCSDS_HEADER_LEN + 1 {
/// Create a struct from a raw slice where the fields have network endianness (big).
/// This function also returns the remaining part of the passed slice starting past the read
/// CCSDS header.
pub fn from_be_bytes(buf: &[u8]) -> Result<(Self, &[u8]), ByteConversionError> {
if buf.len() < CCSDS_HEADER_LEN {
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
found: buf.len(),
expected: CCSDS_HEADER_LEN + 1,
expected: CCSDS_HEADER_LEN,
}));
}
let zc_header = zc::SpHeader::from_bytes(&buf[0..CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyFromError)?;
Ok(Self::from(zc_header))
Ok((Self::from(zc_header), &buf[CCSDS_HEADER_LEN..]))
}
/// Write the header to a raw buffer using big endian format. This function returns the
/// remaining part of the passed slice starting past the written CCSDS header.
pub fn write_to_be_bytes<'a>(
&self,
buf: &'a mut [u8],
) -> Result<&'a mut [u8], ByteConversionError> {
if buf.len() < CCSDS_HEADER_LEN {
return Err(ByteConversionError::FromSliceTooSmall(SizeMissmatch {
found: buf.len(),
expected: CCSDS_HEADER_LEN,
}));
}
let zc_header: zc::SpHeader = zc::SpHeader::from(*self);
zc_header
.to_bytes(&mut buf[0..CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyToError)?;
Ok(&mut buf[CCSDS_HEADER_LEN..])
}
}
@ -540,23 +712,75 @@ pub mod zc {
sph_from_other!(SpHeader, crate::SpHeader);
}
#[cfg(test)]
#[cfg(all(test, feature = "std"))]
mod tests {
#[cfg(feature = "std")]
#[cfg(feature = "serde")]
use crate::CcsdsPrimaryHeader;
use crate::SpHeader;
use crate::{
packet_type_in_raw_packet_id, zc, CcsdsPacket, PacketId, PacketSequenceCtrl, PacketType,
SequenceFlags,
};
use crate::{SequenceFlags, SpHeader};
use alloc::vec;
#[cfg(not(feature = "std"))]
use num::pow;
#[cfg(feature = "std")]
use num_traits::pow;
use postcard::from_bytes;
#[cfg(feature = "alloc")]
use postcard::to_allocvec;
#[cfg(feature = "serde")]
use postcard::{from_bytes, to_allocvec};
const CONST_SP: SpHeader = SpHeader::new(
PacketId::const_tc(true, 0x36),
PacketSequenceCtrl::const_new(SequenceFlags::ContinuationSegment, 0x88),
0x90,
);
const PACKET_ID_TM: PacketId = PacketId::const_tm(true, 0x22);
#[test]
fn verify_const_packet_id() {
assert_eq!(PACKET_ID_TM.apid(), 0x22);
assert_eq!(PACKET_ID_TM.sec_header_flag, true);
assert_eq!(PACKET_ID_TM.ptype, PacketType::Tm);
let const_tc_id = PacketId::const_tc(true, 0x23);
assert_eq!(const_tc_id.ptype, PacketType::Tc);
}
#[test]
fn test_default_packet_id() {
let id_default = PacketId::default();
assert_eq!(id_default.ptype, PacketType::Tm);
assert_eq!(id_default.apid, 0x000);
assert_eq!(id_default.sec_header_flag, false);
}
#[test]
fn test_packet_id_ctors() {
let packet_id = PacketId::new(PacketType::Tc, true, 0x1ff);
assert!(packet_id.is_some());
let packet_id = packet_id.unwrap();
assert_eq!(packet_id.apid(), 0x1ff);
assert_eq!(packet_id.ptype, PacketType::Tc);
assert_eq!(packet_id.sec_header_flag, true);
let packet_id_tc = PacketId::tc(true, 0x1ff);
assert!(packet_id_tc.is_some());
let packet_id_tc = packet_id_tc.unwrap();
assert_eq!(packet_id_tc, packet_id);
let packet_id_tm = PacketId::tm(true, 0x2ff);
assert!(packet_id_tm.is_some());
let packet_id_tm = packet_id_tm.unwrap();
assert_eq!(packet_id_tm.sec_header_flag, true);
assert_eq!(packet_id_tm.ptype, PacketType::Tm);
assert_eq!(packet_id_tm.apid, 0x2ff);
}
#[test]
fn verify_const_sp_header() {
assert_eq!(CONST_SP.sec_header_flag(), true);
assert_eq!(CONST_SP.apid(), 0x36);
assert_eq!(
CONST_SP.sequence_flags(),
SequenceFlags::ContinuationSegment
);
assert_eq!(CONST_SP.seq_count(), 0x88);
assert_eq!(CONST_SP.data_len, 0x90);
}
#[test]
fn test_seq_flag_helpers() {
@ -639,9 +863,9 @@ mod tests {
}
#[test]
#[cfg(feature = "std")]
#[cfg(feature = "serde")]
fn test_serde_sph() {
let sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header");
let sp_header = SpHeader::tc_unseg(0x42, 12, 0).expect("Error creating SP header");
assert_eq!(sp_header.ccsds_version(), 0b000);
assert!(sp_header.is_tc());
assert!(!sp_header.sec_header_flag());
@ -663,7 +887,7 @@ mod tests {
assert_eq!(sp_header.ccsds_version(), 0b000);
assert_eq!(sp_header.data_len, 0);
let sp_header = SpHeader::tm(0x7, 22, 36).expect("Error creating SP header");
let sp_header = SpHeader::tm_unseg(0x7, 22, 36).expect("Error creating SP header");
assert_eq!(sp_header.ccsds_version(), 0b000);
assert!(sp_header.is_tm());
assert!(!sp_header.sec_header_flag());
@ -694,28 +918,69 @@ mod tests {
}
#[test]
fn test_sp_header_setters() {
let mut sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header");
assert_eq!(sp_header.apid(), 0x42);
fn test_setters() {
let sp_header = SpHeader::tc(0x42, SequenceFlags::Unsegmented, 25, 0);
assert!(sp_header.is_some());
let mut sp_header = sp_header.unwrap();
sp_header.set_apid(0x12);
assert_eq!(sp_header.apid(), 0x12);
sp_header.set_sec_header_flag();
assert!(sp_header.sec_header_flag());
sp_header.clear_sec_header_flag();
assert!(!sp_header.sec_header_flag());
sp_header.set_seq_count(0x45);
assert_eq!(sp_header.seq_count(), 0x45);
assert_eq!(sp_header.ptype(), PacketType::Tc);
sp_header.set_packet_type(PacketType::Tm);
assert_eq!(sp_header.ptype(), PacketType::Tm);
sp_header.set_seq_count(0x45);
assert_eq!(sp_header.seq_count(), 0x45);
}
#[test]
fn test_tc_ctor() {
let sp_header = SpHeader::tc(0x42, SequenceFlags::Unsegmented, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tc, &sp_header);
}
#[test]
fn test_tc_ctor_unseg() {
let sp_header = SpHeader::tc_unseg(0x42, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tc, &sp_header);
}
#[test]
fn test_tm_ctor() {
let sp_header = SpHeader::tm(0x42, SequenceFlags::Unsegmented, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tm, &sp_header);
}
#[test]
fn test_tm_ctor_unseg() {
let sp_header = SpHeader::tm_unseg(0x42, 25, 0);
assert!(sp_header.is_some());
let sp_header = sp_header.unwrap();
verify_sp_fields(PacketType::Tm, &sp_header);
}
fn verify_sp_fields(ptype: PacketType, sp_header: &SpHeader) {
assert_eq!(sp_header.ptype(), ptype);
assert_eq!(sp_header.sequence_flags(), SequenceFlags::Unsegmented);
assert_eq!(sp_header.apid(), 0x42);
assert_eq!(sp_header.seq_count(), 25);
assert_eq!(sp_header.data_len(), 0);
}
#[test]
fn test_zc_sph() {
use zerocopy::AsBytes;
let sp_header = SpHeader::tc(0x7FF, pow(2, 14) - 1, 0).expect("Error creating SP header");
let sp_header =
SpHeader::tc_unseg(0x7FF, pow(2, 14) - 1, 0).expect("Error creating SP header");
assert_eq!(sp_header.ptype(), PacketType::Tc);
assert_eq!(sp_header.apid(), 0x7FF);
assert_eq!(sp_header.data_len(), 0);

View File

@ -9,7 +9,7 @@
//! use spacepackets::ecss::PusPacket;
//!
//! // Create a ping telecommand with no user application data
//! let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap();
//! let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
//! let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
//! let pus_tc = PusTc::new(&mut sph, tc_header, None, true);
//! println!("{:?}", pus_tc);
@ -41,6 +41,7 @@ use crate::{
};
use core::mem::size_of;
use delegate::delegate;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zerocopy::AsBytes;
@ -66,7 +67,7 @@ pub const ACK_ALL: u8 = AckOpts::Acceptance as u8
| AckOpts::Progress as u8
| AckOpts::Completion as u8;
pub trait PusTcSecondaryHeaderT {
pub trait GenericPusTcSecondaryHeader {
fn pus_version(&self) -> PusVersion;
fn ack_flags(&self) -> u8;
fn service(&self) -> u8;
@ -76,7 +77,7 @@ pub trait PusTcSecondaryHeaderT {
pub mod zc {
use crate::ecss::{PusError, PusVersion};
use crate::tc::PusTcSecondaryHeaderT;
use crate::tc::GenericPusTcSecondaryHeader;
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
#[derive(FromBytes, AsBytes, Unaligned)]
@ -103,7 +104,7 @@ pub mod zc {
}
}
impl PusTcSecondaryHeaderT for PusTcSecondaryHeader {
impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader {
fn pus_version(&self) -> PusVersion {
PusVersion::try_from(self.version_ack >> 4 & 0b1111).unwrap_or(PusVersion::Invalid)
}
@ -136,7 +137,8 @@ pub mod zc {
}
}
#[derive(PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Debug)]
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTcSecondaryHeader {
pub service: u8,
pub subservice: u8,
@ -145,7 +147,7 @@ pub struct PusTcSecondaryHeader {
pub version: PusVersion,
}
impl PusTcSecondaryHeaderT for PusTcSecondaryHeader {
impl GenericPusTcSecondaryHeader for PusTcSecondaryHeader {
fn pus_version(&self) -> PusVersion {
self.version
}
@ -206,25 +208,26 @@ impl PusTcSecondaryHeader {
/// This class models a PUS telecommand. It is the primary data structure to generate the raw byte
/// representation of a PUS telecommand or to deserialize from one from raw bytes.
///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait which allows
/// to send around TC packets in a raw byte format using a serde provider like
/// [postcard](https://docs.rs/postcard/latest/postcard/).
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
/// [serde] feature is used, which allows to send around TC packets in a raw byte format using a
/// serde provider like [postcard](https://docs.rs/postcard/latest/postcard/).
///
/// There is no spare bytes support yet.
#[derive(PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Debug)]
pub struct PusTc<'slice> {
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTc<'app_data> {
sp_header: SpHeader,
pub sec_header: PusTcSecondaryHeader,
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
pub calc_crc_on_serialization: bool,
#[serde(skip)]
raw_data: Option<&'slice [u8]>,
app_data: Option<&'slice [u8]>,
#[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'app_data [u8]>,
app_data: Option<&'app_data [u8]>,
crc16: Option<u16>,
}
impl<'slice> PusTc<'slice> {
impl<'app_data> PusTc<'app_data> {
/// Generates a new struct instance.
///
/// # Arguments
@ -240,7 +243,7 @@ impl<'slice> PusTc<'slice> {
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTcSecondaryHeader,
app_data: Option<&'slice [u8]>,
app_data: Option<&'app_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tc);
@ -260,12 +263,12 @@ impl<'slice> PusTc<'slice> {
}
/// Simplified version of the [PusTc::new] function which allows to only specify service and
/// subservice instead of the full PUS TC secondary header
/// subservice instead of the full PUS TC secondary header.
pub fn new_simple(
sph: &mut SpHeader,
service: u8,
subservice: u8,
app_data: Option<&'slice [u8]>,
app_data: Option<&'app_data [u8]>,
set_ccsds_len: bool,
) -> Self {
Self::new(
@ -276,6 +279,10 @@ impl<'slice> PusTc<'slice> {
)
}
pub fn sp_header(&self) -> &SpHeader {
&self.sp_header
}
pub fn len_packed(&self) -> usize {
let mut length = PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
if let Some(app_data) = self.app_data {
@ -303,7 +310,7 @@ impl<'slice> PusTc<'slice> {
/// used.
/// If this was not done or the application data is set or changed after construction,
/// this function needs to be called to ensure that the data length field of the CCSDS header
/// is set correctly
/// is set correctly.
pub fn update_ccsds_data_len(&mut self) {
self.sp_header.data_len =
self.len_packed() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
@ -323,7 +330,7 @@ impl<'slice> PusTc<'slice> {
self.crc16 = Some(digest.finalize())
}
/// This helper function calls both [PusTc.update_ccsds_data_len] and [PusTc.calc_own_crc16]
/// This helper function calls both [PusTc::update_ccsds_data_len] and [PusTc::calc_own_crc16].
pub fn update_packet_fields(&mut self) {
self.update_ccsds_data_len();
self.calc_own_crc16();
@ -332,7 +339,6 @@ impl<'slice> PusTc<'slice> {
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0;
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let tc_header_len = size_of::<zc::PusTcSecondaryHeader>();
let total_size = self.len_packed();
if total_size > slice.len() {
@ -342,10 +348,7 @@ impl<'slice> PusTc<'slice> {
})
.into());
}
sph_zc
.to_bytes(&mut slice[curr_idx..curr_idx + CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyToError)?;
self.sp_header.write_to_be_bytes(slice)?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header = zc::PusTcSecondaryHeader::try_from(self.sec_header).unwrap();
sec_header
@ -401,18 +404,16 @@ impl<'slice> PusTc<'slice> {
}
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet
pub fn from_bytes(slice: &'slice [u8]) -> Result<(Self, usize), PusError> {
/// the instance and the found byte length of the packet.
pub fn from_bytes(slice: &'app_data [u8]) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(PusError::RawDataTooShort(raw_data_len));
}
let mut current_idx = 0;
let sph =
crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyFromError)?;
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
current_idx += CCSDS_HEADER_LEN;
let total_len = sph.total_len();
let total_len = sp_header.total_len();
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(PusError::RawDataTooShort(raw_data_len));
}
@ -423,7 +424,7 @@ impl<'slice> PusTc<'slice> {
current_idx += PUC_TC_SECONDARY_HEADER_LEN;
let raw_data = &slice[0..total_len];
let pus_tc = PusTc {
sp_header: SpHeader::from(sph),
sp_header,
sec_header: PusTcSecondaryHeader::try_from(sec_header).unwrap(),
raw_data: Some(raw_data),
app_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
@ -434,7 +435,7 @@ impl<'slice> PusTc<'slice> {
Ok((pus_tc, total_len))
}
pub fn raw(&self) -> Option<&'slice [u8]> {
pub fn raw(&self) -> Option<&'app_data [u8]> {
self.raw_data
}
}
@ -462,7 +463,7 @@ impl PusPacket for PusTc<'_> {
}
//noinspection RsTraitImplementation
impl PusTcSecondaryHeaderT for PusTc<'_> {
impl GenericPusTcSecondaryHeader for PusTc<'_> {
delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8;
@ -472,29 +473,29 @@ impl PusTcSecondaryHeaderT for PusTc<'_> {
});
}
#[cfg(test)]
#[cfg(all(test, feature = "std"))]
mod tests {
use crate::ecss::PusVersion::PusC;
use crate::ecss::{PusError, PusPacket};
use crate::tc::ACK_ALL;
use crate::tc::{PusTc, PusTcSecondaryHeader, PusTcSecondaryHeaderT};
use crate::tc::{GenericPusTcSecondaryHeader, PusTc, PusTcSecondaryHeader};
use crate::{ByteConversionError, SpHeader};
use crate::{CcsdsPacket, SequenceFlags};
use alloc::vec::Vec;
fn base_ping_tc_full_ctor() -> PusTc<'static> {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap();
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
let tc_header = PusTcSecondaryHeader::new_simple(17, 1);
PusTc::new(&mut sph, tc_header, None, true)
}
fn base_ping_tc_simple_ctor() -> PusTc<'static> {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap();
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
PusTc::new_simple(&mut sph, 17, 1, None, true)
}
fn base_ping_tc_simple_ctor_with_app_data(app_data: &'static [u8]) -> PusTc<'static> {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap();
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
PusTc::new_simple(&mut sph, 17, 1, Some(app_data), true)
}
@ -534,7 +535,7 @@ mod tests {
#[test]
fn test_update_func() {
let mut sph = SpHeader::tc(0x02, 0x34, 0).unwrap();
let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
let mut tc = PusTc::new_simple(&mut sph, 17, 1, None, false);
tc.calc_crc_on_serialization = false;
assert_eq!(tc.data_len(), 0);
@ -560,7 +561,6 @@ mod tests {
}
#[test]
#[cfg(feature = "alloc")]
fn test_vec_ser_deser() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_vec = Vec::new();
@ -625,7 +625,7 @@ mod tests {
}
#[test]
fn test_write_buf_too_msall() {
fn test_write_buf_too_small() {
let pus_tc = base_ping_tc_simple_ctor();
let mut test_buf = [0; 12];
let res = pus_tc.write_to_bytes(test_buf.as_mut_slice());
@ -698,7 +698,7 @@ mod tests {
assert_eq!(tc.apid(), 0x02);
assert_eq!(tc.ack_flags(), ACK_ALL);
assert_eq!(tc.len_packed(), exp_full_len);
let mut comp_header = SpHeader::tc(0x02, 0x34, exp_full_len as u16 - 7).unwrap();
let mut comp_header = SpHeader::tc_unseg(0x02, 0x34, exp_full_len as u16 - 7).unwrap();
comp_header.set_sec_header_flag();
assert_eq!(tc.sp_header, comp_header);
}

View File

@ -1,469 +0,0 @@
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
use crate::{ByteConversionError, SizeMissmatch};
use chrono::{DateTime, LocalResult, TimeZone, Utc};
#[allow(unused_imports)]
#[cfg(not(feature = "std"))]
use num_traits::float::FloatCore;
use crate::time::CcsdsTimeCodes::Cds;
#[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError};
pub const CDS_SHORT_LEN: usize = 7;
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
pub const SECONDS_PER_DAY: u32 = 86400;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum CcsdsTimeCodes {
None = 0,
CucCcsdsEpoch = 0b001,
CucAgencyEpoch = 0b010,
Cds = 0b100,
Ccs = 0b101,
}
const CDS_SHORT_P_FIELD: u8 = (CcsdsTimeCodes::Cds as u8) << 4;
impl TryFrom<u8> for CcsdsTimeCodes {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
x if x == CcsdsTimeCodes::None as u8 => Ok(CcsdsTimeCodes::None),
x if x == CcsdsTimeCodes::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCodes::CucCcsdsEpoch),
x if x == CcsdsTimeCodes::CucAgencyEpoch as u8 => Ok(CcsdsTimeCodes::CucAgencyEpoch),
x if x == CcsdsTimeCodes::Cds as u8 => Ok(CcsdsTimeCodes::Cds),
x if x == CcsdsTimeCodes::Ccs as u8 => Ok(CcsdsTimeCodes::Ccs),
_ => Err(()),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TimestampError {
/// Contains tuple where first value is the expected time code and the second
/// value is the found raw value
InvalidTimeCode(CcsdsTimeCodes, u8),
OtherPacketError(ByteConversionError),
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn seconds_since_epoch() -> f64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("System time generation failed")
.as_secs_f64()
}
/// Convert UNIX days to CCSDS days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
pub const fn unix_to_ccsds_days(unix_days: i32) -> i32 {
unix_days - DAYS_CCSDS_TO_UNIX
}
/// Convert CCSDS days to UNIX days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
pub const fn ccsds_to_unix_days(ccsds_days: i32) -> i32 {
ccsds_days + DAYS_CCSDS_TO_UNIX
}
pub trait TimeWriter {
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<(), TimestampError>;
}
pub trait TimeReader {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>
where
Self: Sized;
}
/// Trait for generic CCSDS time providers
pub trait CcsdsTimeProvider {
fn len_as_bytes(&self) -> usize;
/// Returns the pfield of the time provider. The pfield can have one or two bytes depending
/// on the extension bit (first bit). The time provider should returns a tuple where the first
/// entry denotes the length of the pfield and the second entry is the value of the pfield
/// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64;
fn date_time(&self) -> Option<DateTime<Utc>>;
}
/// This object is the abstraction for the CCSDS Day Segmented Time Code (CDS).
///
/// It has the capability to generate and read timestamps as specified in the CCSDS 301.0-B-4
/// section 3.3
///
/// # Example
///
/// ```
/// use spacepackets::time::{CdsShortTimeProvider, TimeWriter};
/// use spacepackets::time::CcsdsTimeCodes::Cds;
/// let timestamp_now = CdsShortTimeProvider::from_now().unwrap();
/// let mut raw_stamp = [0; 7];
/// timestamp_now.write_to_bytes(&mut raw_stamp).unwrap();
/// assert_eq!((raw_stamp[0] >> 4) & 0b111, Cds as u8);
/// ```
#[derive(Debug, Copy, Clone, Default)]
pub struct CdsShortTimeProvider {
ccsds_days: u16,
ms_of_day: u32,
unix_seconds: i64,
date_time: Option<DateTime<Utc>>,
}
impl CdsShortTimeProvider {
pub fn new(ccsds_days: u16, ms_of_day: u32) -> Self {
let provider = Self {
ccsds_days,
ms_of_day,
unix_seconds: 0,
date_time: None,
};
let unix_days_seconds =
ccsds_to_unix_days(ccsds_days as i32) as i64 * SECONDS_PER_DAY as i64;
provider.setup(unix_days_seconds as i64, ms_of_day.into())
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs();
let secs_of_day = epoch % SECONDS_PER_DAY as u64;
let unix_days_seconds = epoch - secs_of_day;
let ms_of_day = secs_of_day * 1000 + now.subsec_millis() as u64;
let provider = Self {
ccsds_days: unix_to_ccsds_days((unix_days_seconds / SECONDS_PER_DAY as u64) as i32)
as u16,
ms_of_day: ms_of_day as u32,
unix_seconds: 0,
date_time: None,
};
Ok(provider.setup(unix_days_seconds as i64, ms_of_day))
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn update_from_now(&mut self) -> Result<(), SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs();
let secs_of_day = epoch % SECONDS_PER_DAY as u64;
let unix_days_seconds = epoch - secs_of_day;
let ms_of_day = secs_of_day * 1000 + now.subsec_millis() as u64;
self.setup(unix_days_seconds as i64, ms_of_day);
Ok(())
}
fn setup(mut self, unix_days_seconds: i64, ms_of_day: u64) -> Self {
self.calc_unix_seconds(unix_days_seconds, ms_of_day);
self.calc_date_time((ms_of_day % 1000) as u32);
self
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn ms_of_day_using_sysclock() -> u32 {
Self::ms_of_day(seconds_since_epoch())
}
pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
let fraction_ms = seconds_since_epoch - seconds_since_epoch.floor();
let ms_of_day: u32 =
(((seconds_since_epoch.floor() as u32 % SECONDS_PER_DAY) * 1000) as f64 + fraction_ms)
.floor() as u32;
ms_of_day
}
fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u64) {
self.unix_seconds = unix_days_seconds;
let seconds_of_day = (ms_of_day / 1000) as i64;
if self.unix_seconds < 0 {
self.unix_seconds -= seconds_of_day;
} else {
self.unix_seconds += seconds_of_day;
}
}
fn calc_date_time(&mut self, ms_since_last_second: u32) {
assert!(ms_since_last_second < 1000, "Invalid MS since last second");
let ns_since_last_sec = ms_since_last_second * 1e6 as u32;
if let LocalResult::Single(val) = Utc.timestamp_opt(self.unix_seconds, ns_since_last_sec) {
self.date_time = Some(val);
} else {
self.date_time = None;
}
}
}
impl CcsdsTimeProvider for CdsShortTimeProvider {
fn len_as_bytes(&self) -> usize {
CDS_SHORT_LEN
}
fn p_field(&self) -> (usize, [u8; 2]) {
(1, [CDS_SHORT_P_FIELD, 0])
}
fn ccdsd_time_code(&self) -> CcsdsTimeCodes {
Cds
}
fn unix_seconds(&self) -> i64 {
self.unix_seconds
}
fn date_time(&self) -> Option<DateTime<Utc>> {
self.date_time
}
}
impl TimeWriter for CdsShortTimeProvider {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<(), TimestampError> {
if buf.len() < self.len_as_bytes() {
return Err(TimestampError::OtherPacketError(
ByteConversionError::ToSliceTooSmall(SizeMissmatch {
expected: self.len_as_bytes(),
found: buf.len(),
}),
));
}
buf[0] = CDS_SHORT_P_FIELD;
buf[1..3].copy_from_slice(self.ccsds_days.to_be_bytes().as_slice());
buf[3..7].copy_from_slice(self.ms_of_day.to_be_bytes().as_slice());
Ok(())
}
}
impl TimeReader for CdsShortTimeProvider {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> {
if buf.len() < CDS_SHORT_LEN {
return Err(TimestampError::OtherPacketError(
ByteConversionError::FromSliceTooSmall(SizeMissmatch {
expected: CDS_SHORT_LEN,
found: buf.len(),
}),
));
}
let pfield = buf[0];
match CcsdsTimeCodes::try_from(pfield >> 4 & 0b111) {
Ok(cds_type) => match cds_type {
Cds => (),
_ => return Err(TimestampError::InvalidTimeCode(Cds, cds_type as u8)),
},
_ => return Err(TimestampError::InvalidTimeCode(Cds, pfield >> 4 & 0b111)),
};
let ccsds_days: u16 = u16::from_be_bytes(buf[1..3].try_into().unwrap());
let ms_of_day: u32 = u32::from_be_bytes(buf[3..7].try_into().unwrap());
Ok(Self::new(ccsds_days, ms_of_day))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::time::TimestampError::{InvalidTimeCode, OtherPacketError};
use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall};
use alloc::format;
use chrono::{Datelike, Timelike};
#[test]
fn test_creation() {
assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX), 0);
assert_eq!(ccsds_to_unix_days(0), DAYS_CCSDS_TO_UNIX);
}
#[cfg(feature = "std")]
#[test]
fn test_get_current_time() {
let sec_floats = seconds_since_epoch();
assert!(sec_floats > 0.0);
}
#[test]
fn test_time_stamp_zero_args() {
let time_stamper = CdsShortTimeProvider::new(0, 0);
assert_eq!(
time_stamper.unix_seconds(),
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
);
assert_eq!(time_stamper.ccdsd_time_code(), Cds);
assert_eq!(time_stamper.p_field(), (1, [(Cds as u8) << 4, 0]));
let date_time = time_stamper.date_time().unwrap();
assert_eq!(date_time.year(), 1958);
assert_eq!(date_time.month(), 1);
assert_eq!(date_time.day(), 1);
assert_eq!(date_time.hour(), 0);
assert_eq!(date_time.minute(), 0);
assert_eq!(date_time.second(), 0);
}
#[test]
fn test_time_stamp_unix_epoch() {
let time_stamper = CdsShortTimeProvider::new((-DAYS_CCSDS_TO_UNIX) as u16, 0);
assert_eq!(time_stamper.unix_seconds(), 0);
let date_time = time_stamper.date_time().unwrap();
assert_eq!(date_time.year(), 1970);
assert_eq!(date_time.month(), 1);
assert_eq!(date_time.day(), 1);
assert_eq!(date_time.hour(), 0);
assert_eq!(date_time.minute(), 0);
assert_eq!(date_time.second(), 0);
}
#[test]
fn test_write() {
let mut buf = [0; 16];
let time_stamper_0 = CdsShortTimeProvider::new(0, 0);
let mut res = time_stamper_0.write_to_bytes(&mut buf);
assert!(res.is_ok());
assert_eq!(buf[0], (CcsdsTimeCodes::Cds as u8) << 4);
assert_eq!(
u16::from_be_bytes(buf[1..3].try_into().expect("Byte conversion failed")),
0
);
assert_eq!(
u32::from_be_bytes(buf[3..7].try_into().expect("Byte conversion failed")),
0
);
let time_stamper_1 = CdsShortTimeProvider::new(u16::MAX - 1, u32::MAX - 1);
res = time_stamper_1.write_to_bytes(&mut buf);
assert!(res.is_ok());
assert_eq!(buf[0], (CcsdsTimeCodes::Cds as u8) << 4);
assert_eq!(
u16::from_be_bytes(buf[1..3].try_into().expect("Byte conversion failed")),
u16::MAX - 1
);
assert_eq!(
u32::from_be_bytes(buf[3..7].try_into().expect("Byte conversion failed")),
u32::MAX - 1
);
}
#[test]
fn test_faulty_write_buf_too_small() {
let mut buf = [0; 7];
let time_stamper = CdsShortTimeProvider::new(u16::MAX - 1, u32::MAX - 1);
for i in 0..6 {
let res = time_stamper.write_to_bytes(&mut buf[0..i]);
assert!(res.is_err());
match res.unwrap_err() {
OtherPacketError(ToSliceTooSmall(missmatch)) => {
assert_eq!(missmatch.found, i);
assert_eq!(missmatch.expected, 7);
}
_ => panic!(
"{}",
format!("Invalid error {:?} detected", res.unwrap_err())
),
}
}
}
#[test]
fn test_faulty_read_buf_too_small() {
let buf = [0; 7];
for i in 0..6 {
let res = CdsShortTimeProvider::from_bytes(&buf[0..i]);
assert!(res.is_err());
match res.unwrap_err() {
InvalidTimeCode(_, _) => {
panic!("Unexpected error");
}
OtherPacketError(e) => match e {
FromSliceTooSmall(missmatch) => {
assert_eq!(missmatch.found, i);
assert_eq!(missmatch.expected, 7);
}
_ => panic!("{}", format!("Invalid error {:?} detected", e)),
},
}
}
}
#[test]
fn test_faulty_invalid_pfield() {
let mut buf = [0; 16];
let time_stamper_0 = CdsShortTimeProvider::new(0, 0);
let res = time_stamper_0.write_to_bytes(&mut buf);
assert!(res.is_ok());
buf[0] = 0;
let res = CdsShortTimeProvider::from_bytes(&buf);
assert!(res.is_err());
let err = res.unwrap_err();
match err {
InvalidTimeCode(code, raw) => {
assert_eq!(code, Cds);
assert_eq!(raw, 0);
}
OtherPacketError(_) => {}
}
}
#[test]
fn test_reading() {
let mut buf = [0; 16];
let time_stamper = CdsShortTimeProvider::new(u16::MAX - 1, u32::MAX - 1);
let res = time_stamper.write_to_bytes(&mut buf);
assert!(res.is_ok());
assert_eq!(buf[0], (CcsdsTimeCodes::Cds as u8) << 4);
assert_eq!(
u16::from_be_bytes(buf[1..3].try_into().expect("Byte conversion failed")),
u16::MAX - 1
);
assert_eq!(
u32::from_be_bytes(buf[3..7].try_into().expect("Byte conversion failed")),
u32::MAX - 1
);
let read_stamp = CdsShortTimeProvider::from_bytes(&buf).expect("Reading timestamp failed");
assert_eq!(read_stamp.ccsds_days, u16::MAX - 1);
assert_eq!(read_stamp.ms_of_day, u32::MAX - 1);
}
#[cfg(feature = "std")]
#[test]
fn test_time_now() {
let timestamp_now = CdsShortTimeProvider::from_now().unwrap();
let compare_stamp = Utc::now();
let dt = timestamp_now.date_time().unwrap();
if compare_stamp.year() > dt.year() {
assert_eq!(compare_stamp.year() - dt.year(), 1);
} else {
assert_eq!(dt.year(), compare_stamp.year());
}
generic_dt_property_equality_check(dt.month(), compare_stamp.month(), 1, 12);
assert_eq!(dt.day(), compare_stamp.day());
if compare_stamp.day() < dt.day() {
assert!(dt.day() >= 28);
assert_eq!(compare_stamp.day(), 1);
} else if compare_stamp.day() > dt.day() {
assert_eq!(compare_stamp.day() - dt.day(), 1);
} else {
assert_eq!(compare_stamp.day(), dt.day());
}
generic_dt_property_equality_check(dt.hour(), compare_stamp.hour(), 0, 23);
generic_dt_property_equality_check(dt.minute(), compare_stamp.minute(), 0, 59);
}
#[cfg(feature = "std")]
fn generic_dt_property_equality_check(first: u32, second: u32, start: u32, end: u32) {
if second < first {
assert_eq!(second, start);
assert_eq!(first, end);
} else if second > first {
assert_eq!(second - first, 1);
} else {
assert_eq!(first, second);
}
}
}

130
src/time/ascii.rs Normal file
View File

@ -0,0 +1,130 @@
//! Module to generate the ASCII timecodes specified in
//! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.5 .
//! See [chrono::DateTime::format] for a usage example of the generated
//! [chrono::format::DelayedFormat] structs.
#[cfg(feature = "alloc")]
use chrono::{
format::{DelayedFormat, StrftimeItems},
DateTime, Utc,
};
/// Tuple of format string and formatted size for time code A.
///
/// Format: YYYY-MM-DDThh:mm:ss.ddd
///
/// Three digits are used for the decimal fraction
pub const FMT_STR_CODE_A_WITH_SIZE: (&str, usize) = ("%FT%T%.3f", 23);
/// Tuple of format string and formatted size for time code A.
///
/// Format: YYYY-MM-DDThh:mm:ss.dddZ
///
/// Three digits are used for the decimal fraction and a terminator is added at the end.
pub const FMT_STR_CODE_A_TERMINATED_WITH_SIZE: (&str, usize) = ("%FT%T%.3fZ", 24);
/// Tuple of format string and formatted size for time code A.
///
/// Format: YYYY-DDDThh:mm:ss.ddd
///
/// Three digits are used for the decimal fraction
pub const FMT_STR_CODE_B_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3f", 21);
/// Tuple of format string and formatted size for time code A.
///
/// Format: YYYY-DDDThh:mm:ss.dddZ
///
/// Three digits are used for the decimal fraction and a terminator is added at the end.
pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22);
/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_a_terminated(
date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_WITH_SIZE.0)
}
/// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format.
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn generate_time_code_b_terminated(
date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
use std::format;
#[test]
fn test_ascii_timestamp_a_unterminated() {
let date = Utc::now();
let stamp_formatter = generate_time_code_a(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 10);
assert_eq!(stamp.len(), FMT_STR_CODE_A_WITH_SIZE.1);
}
#[test]
fn test_ascii_timestamp_a_terminated() {
let date = Utc::now();
let stamp_formatter = generate_time_code_a_terminated(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 10);
let z_terminator = stamp.find("Z");
assert!(z_terminator.is_some());
assert_eq!(
z_terminator.unwrap(),
FMT_STR_CODE_A_TERMINATED_WITH_SIZE.1 - 1
);
assert_eq!(stamp.len(), FMT_STR_CODE_A_TERMINATED_WITH_SIZE.1);
}
#[test]
fn test_ascii_timestamp_b_unterminated() {
let date = Utc::now();
let stamp_formatter = generate_time_code_b(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 8);
assert_eq!(stamp.len(), FMT_STR_CODE_B_WITH_SIZE.1);
}
#[test]
fn test_ascii_timestamp_b_terminated() {
let date = Utc::now();
let stamp_formatter = generate_time_code_b_terminated(&date);
let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T");
assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 8);
let z_terminator = stamp.find("Z");
assert!(z_terminator.is_some());
assert_eq!(
z_terminator.unwrap(),
FMT_STR_CODE_B_TERMINATED_WITH_SIZE.1 - 1
);
assert_eq!(stamp.len(), FMT_STR_CODE_B_TERMINATED_WITH_SIZE.1);
}
}

2260
src/time/cds.rs Normal file

File diff suppressed because it is too large Load Diff

1125
src/time/cuc.rs Normal file

File diff suppressed because it is too large Load Diff

560
src/time/mod.rs Normal file
View File

@ -0,0 +1,560 @@
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
use crate::{ByteConversionError, SizeMissmatch};
use chrono::{DateTime, LocalResult, TimeZone, Utc};
use core::cmp::Ordering;
use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign};
use core::time::Duration;
#[allow(unused_imports)]
#[cfg(not(feature = "std"))]
use num_traits::float::FloatCore;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError};
pub mod ascii;
pub mod cds;
pub mod cuc;
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
pub const SECONDS_PER_DAY: u32 = 86400;
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CcsdsTimeCodes {
CucCcsdsEpoch = 0b001,
CucAgencyEpoch = 0b010,
Cds = 0b100,
Ccs = 0b101,
AgencyDefined = 0b110,
}
impl TryFrom<u8> for CcsdsTimeCodes {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
x if x == CcsdsTimeCodes::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCodes::CucCcsdsEpoch),
x if x == CcsdsTimeCodes::CucAgencyEpoch as u8 => Ok(CcsdsTimeCodes::CucAgencyEpoch),
x if x == CcsdsTimeCodes::Cds as u8 => Ok(CcsdsTimeCodes::Cds),
x if x == CcsdsTimeCodes::Ccs as u8 => Ok(CcsdsTimeCodes::Ccs),
x if x == CcsdsTimeCodes::AgencyDefined as u8 => Ok(CcsdsTimeCodes::AgencyDefined),
_ => Err(()),
}
}
}
/// Retrieve the CCSDS time code from the p-field. If no valid time code identifier is found, the
/// value of the raw time code identification field is returned.
pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCodes, u8> {
let raw_bits = (pfield >> 4) & 0b111;
CcsdsTimeCodes::try_from(raw_bits).map_err(|_| raw_bits)
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum TimestampError {
/// Contains tuple where first value is the expected time code and the second
/// value is the found raw value
InvalidTimeCode(CcsdsTimeCodes, u8),
ByteConversionError(ByteConversionError),
CdsError(cds::CdsError),
CucError(cuc::CucError),
DateBeforeCcsdsEpoch(DateTime<Utc>),
CustomEpochNotSupported,
}
impl From<cds::CdsError> for TimestampError {
fn from(e: cds::CdsError) -> Self {
TimestampError::CdsError(e)
}
}
impl From<cuc::CucError> for TimestampError {
fn from(e: cuc::CucError) -> Self {
TimestampError::CucError(e)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
#[derive(Debug, Clone)]
pub enum StdTimestampError {
SystemTimeError(SystemTimeError),
TimestampError(TimestampError),
}
#[cfg(feature = "std")]
impl From<TimestampError> for StdTimestampError {
fn from(v: TimestampError) -> Self {
Self::TimestampError(v)
}
}
#[cfg(feature = "std")]
impl From<SystemTimeError> for StdTimestampError {
fn from(v: SystemTimeError) -> Self {
Self::SystemTimeError(v)
}
}
impl Display for TimestampError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
TimestampError::InvalidTimeCode(time_code, raw_val) => {
write!(
f,
"invalid raw time code value {} for time code {:?}",
raw_val, time_code
)
}
TimestampError::CdsError(e) => {
write!(f, "cds error {}", e)
}
TimestampError::CucError(e) => {
write!(f, "cuc error {}", e)
}
TimestampError::ByteConversionError(e) => {
write!(f, "byte conversion error {}", e)
}
TimestampError::DateBeforeCcsdsEpoch(e) => {
write!(f, "datetime with date before ccsds epoch: {}", e)
}
TimestampError::CustomEpochNotSupported => {
write!(f, "custom epochs are not supported")
}
}
}
}
#[cfg(feature = "std")]
impl Error for TimestampError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TimestampError::ByteConversionError(e) => Some(e),
TimestampError::CdsError(e) => Some(e),
TimestampError::CucError(e) => Some(e),
_ => None,
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn seconds_since_epoch() -> f64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("System time generation failed")
.as_secs_f64()
}
/// Convert UNIX days to CCSDS days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
pub const fn unix_to_ccsds_days(unix_days: i64) -> i64 {
unix_days - DAYS_CCSDS_TO_UNIX as i64
}
/// Convert CCSDS days to UNIX days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
ccsds_days + DAYS_CCSDS_TO_UNIX as i64
}
/// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased
/// seconds since the CCSDS and UNIX epoch times.
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 {
unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
}
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 {
ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn ms_of_day_using_sysclock() -> u32 {
ms_of_day(seconds_since_epoch())
}
pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
let fraction_ms = seconds_since_epoch - seconds_since_epoch.floor();
let ms_of_day: u32 = (((seconds_since_epoch.floor() as u32 % SECONDS_PER_DAY) * 1000) as f64
+ fraction_ms)
.floor() as u32;
ms_of_day
}
pub trait TimeWriter {
/// Generic function to convert write a timestamp into a raw buffer.
/// Returns the number of written bytes on success.
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>;
}
pub trait TimeReader {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>
where
Self: Sized;
}
/// Trait for generic CCSDS time providers.
///
/// The UNIX helper methods and the [Self::date_time] method are not strictly necessary but extremely
/// practical because they are a very common and simple exchange format for time information.
pub trait CcsdsTimeProvider {
fn len_as_bytes(&self) -> usize;
/// Returns the pfield of the time provider. The pfield can have one or two bytes depending
/// on the extension bit (first bit). The time provider should returns a tuple where the first
/// entry denotes the length of the pfield and the second entry is the value of the pfield
/// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64;
fn subsecond_millis(&self) -> Option<u16>;
fn unix_stamp(&self) -> UnixTimestamp {
UnixTimestamp {
unix_seconds: self.unix_seconds(),
subsecond_millis: self.subsecond_millis(),
}
}
fn date_time(&self) -> Option<DateTime<Utc>>;
}
/// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00.
///
/// Also can optionally include subsecond millisecond for greater accuracy. Please note that a
/// subsecond millisecond value of 0 gets converted to [None].
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnixTimestamp {
pub unix_seconds: i64,
subsecond_millis: Option<u16>,
}
impl UnixTimestamp {
/// Returns none if the subsecond millisecond value is larger than 999. 0 is converted to
/// a [None] value.
pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
if subsec_millis > 999 {
return None;
}
Some(Self::const_new(unix_seconds, subsec_millis))
}
/// Like [Self::new] but const. Panics if the subsecond value is larger than 999.
pub const fn const_new(unix_seconds: i64, subsec_millis: u16) -> Self {
if subsec_millis > 999 {
panic!("subsec milliseconds exceeds 999");
}
let subsecond_millis = if subsec_millis == 0 {
None
} else {
Some(subsec_millis)
};
Self {
unix_seconds,
subsecond_millis,
}
}
pub fn new_only_seconds(unix_seconds: i64) -> Self {
Self {
unix_seconds,
subsecond_millis: None,
}
}
pub fn subsecond_millis(&self) -> Option<u16> {
self.subsecond_millis
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub fn from_now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs();
Ok(UnixTimestamp {
unix_seconds: epoch as i64,
subsecond_millis: Some(now.subsec_millis() as u16),
})
}
#[inline]
pub fn unix_seconds_f64(&self) -> f64 {
let mut secs = self.unix_seconds as f64;
if let Some(subsec_millis) = self.subsecond_millis {
secs += subsec_millis as f64 / 1000.0;
}
secs
}
pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> {
Utc.timestamp_opt(
self.unix_seconds,
self.subsecond_millis.unwrap_or(0) as u32 * 10_u32.pow(6),
)
}
}
impl From<DateTime<Utc>> for UnixTimestamp {
fn from(value: DateTime<Utc>) -> Self {
Self {
unix_seconds: value.timestamp(),
subsecond_millis: Some(value.timestamp_subsec_millis() as u16),
}
}
}
impl PartialOrd for UnixTimestamp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}
match self.unix_seconds.cmp(&other.unix_seconds) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
_ => (),
}
match self
.subsecond_millis()
.unwrap_or(0)
.cmp(&other.subsecond_millis().unwrap_or(0))
{
Ordering::Less => {
return if self.unix_seconds < 0 {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
}
}
Ordering::Greater => {
return if self.unix_seconds < 0 {
Some(Ordering::Less)
} else {
Some(Ordering::Greater)
}
}
Ordering::Equal => (),
}
Some(Ordering::Equal)
}
}
impl Ord for UnixTimestamp {
fn cmp(&self, other: &Self) -> Ordering {
PartialOrd::partial_cmp(self, other).unwrap()
}
}
fn get_new_stamp_after_addition(
current_stamp: &UnixTimestamp,
duration: Duration,
) -> UnixTimestamp {
let mut new_subsec_millis =
current_stamp.subsecond_millis().unwrap_or(0) + duration.subsec_millis() as u16;
let mut new_unix_seconds = current_stamp.unix_seconds;
let mut increment_seconds = |value: u32| {
if new_unix_seconds < 0 {
new_unix_seconds = new_unix_seconds
.checked_sub(value.into())
.expect("new unix seconds would exceed i64::MIN");
} else {
new_unix_seconds = new_unix_seconds
.checked_add(value.into())
.expect("new unix seconds would exceed i64::MAX");
}
};
if new_subsec_millis >= 1000 {
new_subsec_millis -= 1000;
increment_seconds(1);
}
increment_seconds(
duration
.as_secs()
.try_into()
.expect("duration seconds exceeds u32::MAX"),
);
UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis)
}
/// Please note that this operation will panic on the following conditions:
///
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX].
/// - Seconds from duration to add exceeds [u32::MAX].
impl AddAssign<Duration> for UnixTimestamp {
fn add_assign(&mut self, duration: Duration) {
*self = get_new_stamp_after_addition(self, duration);
}
}
/// Please note that this operation will panic for the following conditions:
///
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX].
/// - Unix seconds exceeds [u32::MAX].
impl Add<Duration> for UnixTimestamp {
type Output = Self;
fn add(self, duration: Duration) -> Self::Output {
get_new_stamp_after_addition(&self, duration)
}
}
impl Add<Duration> for &UnixTimestamp {
type Output = UnixTimestamp;
fn add(self, duration: Duration) -> Self::Output {
get_new_stamp_after_addition(self, duration)
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn test_days_conversion() {
assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0);
assert_eq!(ccsds_to_unix_days(0), DAYS_CCSDS_TO_UNIX.into());
}
#[test]
fn test_get_current_time() {
let sec_floats = seconds_since_epoch();
assert!(sec_floats > 0.0);
}
#[test]
fn test_ccsds_epoch() {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let unix_epoch = now.as_secs();
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u64;
assert!(ccsds_epoch > unix_epoch);
assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
}
#[test]
fn basic_unix_stamp_test() {
let stamp = UnixTimestamp::new_only_seconds(-200);
assert_eq!(stamp.unix_seconds, -200);
assert!(stamp.subsecond_millis().is_none());
let stamp = UnixTimestamp::new_only_seconds(250);
assert_eq!(stamp.unix_seconds, 250);
assert!(stamp.subsecond_millis().is_none());
}
#[test]
fn basic_float_unix_stamp_test() {
let stamp = UnixTimestamp::new(500, 600).unwrap();
assert!(stamp.subsecond_millis.is_some());
assert_eq!(stamp.unix_seconds, 500);
let subsec_millis = stamp.subsecond_millis().unwrap();
assert_eq!(subsec_millis, 600);
assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001);
}
#[test]
fn test_ord_larger() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0);
assert!(stamp2 > stamp1);
}
#[test]
fn test_ord_smaller() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp0 < stamp1);
assert!(stamp0 < stamp2);
assert!(stamp1 < stamp2);
}
#[test]
fn test_ord_larger_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp0 > stamp1);
assert!(stamp0 > stamp2);
assert!(stamp1 > stamp2);
assert!(stamp1 >= stamp2);
assert!(stamp0 >= stamp1);
}
#[test]
fn test_ord_smaller_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp2 < stamp1);
assert!(stamp2 < stamp0);
assert!(stamp1 < stamp0);
assert!(stamp1 <= stamp0);
assert!(stamp2 <= stamp1);
}
#[test]
fn test_eq() {
let stamp0 = UnixTimestamp::new(5, 0).unwrap();
let stamp1 = UnixTimestamp::new_only_seconds(5);
assert_eq!(stamp0, stamp1);
assert!(stamp0 <= stamp1);
assert!(stamp0 >= stamp1);
assert!(!(stamp0 < stamp1));
assert!(!(stamp0 > stamp1));
}
#[test]
fn test_addition() {
let mut stamp0 = UnixTimestamp::new_only_seconds(1);
stamp0 += Duration::from_secs(5);
assert_eq!(stamp0.unix_seconds, 6);
assert!(stamp0.subsecond_millis().is_none());
let stamp1 = stamp0 + Duration::from_millis(500);
assert_eq!(stamp1.unix_seconds, 6);
assert!(stamp1.subsecond_millis().is_some());
assert_eq!(stamp1.subsecond_millis().unwrap(), 500);
}
#[test]
fn test_addition_on_ref() {
let stamp0 = &UnixTimestamp::new(20, 500).unwrap();
let stamp1 = stamp0 + Duration::from_millis(2500);
assert_eq!(stamp1.unix_seconds, 23);
assert!(stamp1.subsecond_millis().is_none());
}
#[test]
fn test_addition_spillover() {
let mut stamp0 = UnixTimestamp::new(1, 900).unwrap();
stamp0 += Duration::from_millis(100);
assert_eq!(stamp0.unix_seconds, 2);
assert!(stamp0.subsecond_millis().is_none());
stamp0 += Duration::from_millis(1100);
assert_eq!(stamp0.unix_seconds, 3);
assert_eq!(stamp0.subsecond_millis().unwrap(), 100);
}
}

187
src/tm.rs
View File

@ -9,6 +9,7 @@ use crate::{
CCSDS_HEADER_LEN,
};
use core::mem::size_of;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zerocopy::AsBytes;
@ -21,7 +22,7 @@ pub const PUC_TM_MIN_SEC_HEADER_LEN: usize = 7;
pub const PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA: usize =
CCSDS_HEADER_LEN + PUC_TM_MIN_SEC_HEADER_LEN + size_of::<CrcType>();
pub trait PusTmSecondaryHeaderT {
pub trait GenericPusTmSecondaryHeader {
fn pus_version(&self) -> PusVersion;
fn sc_time_ref_status(&self) -> u8;
fn service(&self) -> u8;
@ -31,6 +32,7 @@ pub trait PusTmSecondaryHeaderT {
}
pub mod zc {
use super::GenericPusTmSecondaryHeader;
use crate::ecss::{PusError, PusVersion};
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
@ -46,7 +48,7 @@ pub mod zc {
pub struct PusTmSecHeader<'slice> {
pub(crate) zc_header: PusTmSecHeaderWithoutTimestamp,
pub(crate) timestamp: &'slice [u8],
pub(crate) timestamp: Option<&'slice [u8]>,
}
impl TryFrom<crate::tm::PusTmSecondaryHeader<'_>> for PusTmSecHeaderWithoutTimestamp {
@ -76,7 +78,7 @@ pub mod zc {
}
}
impl super::PusTmSecondaryHeaderT for PusTmSecHeaderWithoutTimestamp {
impl GenericPusTmSecondaryHeader for PusTmSecHeaderWithoutTimestamp {
fn pus_version(&self) -> PusVersion {
PusVersion::try_from(self.pus_version_and_sc_time_ref_status >> 4 & 0b1111)
.unwrap_or(PusVersion::Invalid)
@ -104,28 +106,26 @@ pub mod zc {
}
}
#[derive(PartialEq, Eq, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct PusTmSecondaryHeader<'slice> {
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTmSecondaryHeader<'stamp> {
pus_version: PusVersion,
pub sc_time_ref_status: u8,
pub service: u8,
pub subservice: u8,
pub msg_counter: u16,
pub dest_id: u16,
pub time_stamp: &'slice [u8],
pub timestamp: Option<&'stamp [u8]>,
}
impl<'slice> PusTmSecondaryHeader<'slice> {
pub fn new_simple(service: u8, subservice: u8, time_stamp: &'slice [u8]) -> Self {
PusTmSecondaryHeader {
pus_version: PusVersion::PusC,
sc_time_ref_status: 0,
service,
subservice,
msg_counter: 0,
dest_id: 0,
time_stamp,
}
impl<'stamp> PusTmSecondaryHeader<'stamp> {
pub fn new_simple(service: u8, subservice: u8, timestamp: &'stamp [u8]) -> Self {
Self::new(service, subservice, 0, 0, Some(timestamp))
}
/// Like [Self::new_simple] but without a timestamp.
pub fn new_simple_no_timestamp(service: u8, subservice: u8) -> Self {
Self::new(service, subservice, 0, 0, None)
}
pub fn new(
@ -133,7 +133,7 @@ impl<'slice> PusTmSecondaryHeader<'slice> {
subservice: u8,
msg_counter: u16,
dest_id: u16,
time_stamp: &'slice [u8],
timestamp: Option<&'stamp [u8]>,
) -> Self {
PusTmSecondaryHeader {
pus_version: PusVersion::PusC,
@ -142,12 +142,12 @@ impl<'slice> PusTmSecondaryHeader<'slice> {
subservice,
msg_counter,
dest_id,
time_stamp,
timestamp,
}
}
}
impl PusTmSecondaryHeaderT for PusTmSecondaryHeader<'_> {
impl GenericPusTmSecondaryHeader for PusTmSecondaryHeader<'_> {
fn pus_version(&self) -> PusVersion {
self.pus_version
}
@ -184,7 +184,7 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
subservice: sec_header.zc_header.subservice(),
msg_counter: sec_header.zc_header.msg_counter(),
dest_id: sec_header.zc_header.dest_id(),
time_stamp: sec_header.timestamp,
timestamp: sec_header.timestamp,
})
}
}
@ -193,25 +193,31 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
/// structure to generate the raw byte representation of PUS telemetry or to
/// deserialize from one from raw bytes.
///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait which allows
/// to send around TM packets in a raw byte format using a serde provider like
/// [postcard](https://docs.rs/postcard/latest/postcard/).
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the [serde]
/// feature is used which allows to send around TM packets in a raw byte format using a serde
/// provider like [postcard](https://docs.rs/postcard/latest/postcard/).
///
/// There is no spare bytes support yet.
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Copy, Clone)]
pub struct PusTm<'slice> {
///
/// # Lifetimes
///
/// * `'src_data` - Life time of a buffer where the user provided time stamp and source data will
/// be serialized into.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTm<'src_data> {
pub sp_header: SpHeader,
pub sec_header: PusTmSecondaryHeader<'slice>,
pub sec_header: PusTmSecondaryHeader<'src_data>,
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
pub calc_crc_on_serialization: bool,
#[serde(skip)]
raw_data: Option<&'slice [u8]>,
source_data: Option<&'slice [u8]>,
#[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'src_data [u8]>,
source_data: Option<&'src_data [u8]>,
crc16: Option<u16>,
}
impl<'slice> PusTm<'slice> {
impl<'src_data> PusTm<'src_data> {
/// Generates a new struct instance.
///
/// # Arguments
@ -226,8 +232,8 @@ impl<'slice> PusTm<'slice> {
/// the correct value to this field manually
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTmSecondaryHeader<'slice>,
source_data: Option<&'slice [u8]>,
sec_header: PusTmSecondaryHeader<'src_data>,
source_data: Option<&'src_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tm);
@ -248,18 +254,20 @@ impl<'slice> PusTm<'slice> {
pub fn len_packed(&self) -> usize {
let mut length = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
length += self.sec_header.time_stamp.len();
if let Some(timestamp) = self.sec_header.timestamp {
length += timestamp.len();
}
if let Some(src_data) = self.source_data {
length += src_data.len();
}
length
}
pub fn time_stamp(&self) -> &'slice [u8] {
self.sec_header.time_stamp
pub fn timestamp(&self) -> Option<&'src_data [u8]> {
self.sec_header.timestamp
}
pub fn source_data(&self) -> Option<&'slice [u8]> {
pub fn source_data(&self) -> Option<&'src_data [u8]> {
self.source_data
}
@ -295,7 +303,9 @@ impl<'slice> PusTm<'slice> {
digest.update(sph_zc.as_bytes());
let pus_tc_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
digest.update(pus_tc_header.as_bytes());
digest.update(self.sec_header.time_stamp);
if let Some(stamp) = self.sec_header.timestamp {
digest.update(stamp);
}
if let Some(src_data) = self.source_data {
digest.update(src_data);
}
@ -311,7 +321,6 @@ impl<'slice> PusTm<'slice> {
/// Write the raw PUS byte representation to a provided buffer.
pub fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = 0;
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let total_size = self.len_packed();
if total_size > slice.len() {
return Err(ByteConversionError::ToSliceTooSmall(SizeMissmatch {
@ -320,10 +329,8 @@ impl<'slice> PusTm<'slice> {
})
.into());
}
sph_zc
.to_bytes(&mut slice[curr_idx..curr_idx + CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyToError)?;
self.sp_header
.write_to_be_bytes(&mut slice[0..CCSDS_HEADER_LEN])?;
curr_idx += CCSDS_HEADER_LEN;
let sec_header_len = size_of::<zc::PusTmSecHeaderWithoutTimestamp>();
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
@ -331,9 +338,11 @@ impl<'slice> PusTm<'slice> {
.write_to_bytes(&mut slice[curr_idx..curr_idx + sec_header_len])
.ok_or(ByteConversionError::ZeroCopyToError)?;
curr_idx += sec_header_len;
let timestamp_len = self.sec_header.time_stamp.len();
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(self.sec_header.time_stamp);
curr_idx += timestamp_len;
if let Some(timestamp) = self.sec_header.timestamp {
let timestamp_len = timestamp.len();
slice[curr_idx..curr_idx + timestamp_len].copy_from_slice(timestamp);
curr_idx += timestamp_len;
}
if let Some(src_data) = self.source_data {
slice[curr_idx..curr_idx + src_data.len()].copy_from_slice(src_data);
curr_idx += src_data.len();
@ -355,8 +364,10 @@ impl<'slice> PusTm<'slice> {
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
let sph_zc = crate::zc::SpHeader::from(self.sp_header);
let mut appended_len =
PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA + self.sec_header.time_stamp.len();
let mut appended_len = PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA;
if let Some(timestamp) = self.sec_header.timestamp {
appended_len += timestamp.len();
}
if let Some(src_data) = self.source_data {
appended_len += src_data.len();
};
@ -368,8 +379,10 @@ impl<'slice> PusTm<'slice> {
let sec_header = zc::PusTmSecHeaderWithoutTimestamp::try_from(self.sec_header).unwrap();
vec.extend_from_slice(sec_header.as_bytes());
ser_len += sec_header.as_bytes().len();
vec.extend_from_slice(self.sec_header.time_stamp);
ser_len += self.sec_header.time_stamp.len();
if let Some(timestamp) = self.sec_header.timestamp {
ser_len += timestamp.len();
vec.extend_from_slice(timestamp);
}
if let Some(src_data) = self.source_data {
vec.extend_from_slice(src_data);
ser_len += src_data.len();
@ -389,7 +402,7 @@ impl<'slice> PusTm<'slice> {
/// the instance and the found byte length of the packet. The timestamp length needs to be
/// known beforehand.
pub fn from_bytes(
slice: &'slice [u8],
slice: &'src_data [u8],
timestamp_len: usize,
) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
@ -397,11 +410,9 @@ impl<'slice> PusTm<'slice> {
return Err(PusError::RawDataTooShort(raw_data_len));
}
let mut current_idx = 0;
let sph =
crate::zc::SpHeader::from_bytes(&slice[current_idx..current_idx + CCSDS_HEADER_LEN])
.ok_or(ByteConversionError::ZeroCopyFromError)?;
let (sp_header, _) = SpHeader::from_be_bytes(&slice[0..CCSDS_HEADER_LEN])?;
current_idx += 6;
let total_len = sph.total_len();
let total_len = sp_header.total_len();
if raw_data_len < total_len || total_len < PUS_TM_MIN_LEN_WITHOUT_SOURCE_DATA {
return Err(PusError::RawDataTooShort(raw_data_len));
}
@ -410,14 +421,18 @@ impl<'slice> PusTm<'slice> {
)
.ok_or(ByteConversionError::ZeroCopyFromError)?;
current_idx += PUC_TM_MIN_SEC_HEADER_LEN;
let mut timestamp = None;
if timestamp_len > 0 {
timestamp = Some(&slice[current_idx..current_idx + timestamp_len]);
}
let zc_sec_header_wrapper = zc::PusTmSecHeader {
zc_header: sec_header_zc,
timestamp: &slice[current_idx..current_idx + timestamp_len],
timestamp,
};
current_idx += timestamp_len;
let raw_data = &slice[0..total_len];
let pus_tm = PusTm {
sp_header: SpHeader::from(sph),
sp_header,
sec_header: PusTmSecondaryHeader::try_from(zc_sec_header_wrapper).unwrap(),
raw_data: Some(&slice[0..total_len]),
source_data: user_data_from_raw(current_idx, total_len, raw_data_len, slice)?,
@ -452,7 +467,7 @@ impl PusPacket for PusTm<'_> {
}
//noinspection RsTraitImplementation
impl PusTmSecondaryHeaderT for PusTm<'_> {
impl GenericPusTmSecondaryHeader for PusTm<'_> {
delegate!(to self.sec_header {
fn pus_version(&self) -> PusVersion;
fn service(&self) -> u8;
@ -469,33 +484,33 @@ mod tests {
use crate::ecss::PusVersion::PusC;
use crate::SpHeader;
fn base_ping_reply_full_ctor(time_stamp: &[u8]) -> PusTm {
let mut sph = SpHeader::tm(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp);
fn base_ping_reply_full_ctor(timestamp: &[u8]) -> PusTm {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &timestamp);
PusTm::new(&mut sph, tc_header, None, true)
}
fn base_hk_reply<'a>(time_stamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
let mut sph = SpHeader::tm(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &time_stamp);
fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(3, 5, &timestamp);
PusTm::new(&mut sph, tc_header, Some(src_data), true)
}
fn dummy_time_stamp() -> &'static [u8] {
fn dummy_timestamp() -> &'static [u8] {
return &[0, 1, 2, 3, 4, 5, 6];
}
#[test]
fn test_basic() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
verify_ping_reply(&pus_tm, false, 22, dummy_time_stamp());
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
verify_ping_reply(&pus_tm, false, 22, dummy_timestamp());
}
#[test]
fn test_serialization_no_source_data() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes(&mut buf)
@ -507,7 +522,7 @@ mod tests {
#[test]
fn test_serialization_with_source_data() {
let src_data = [1, 2, 3];
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
let hk_reply = base_hk_reply(dummy_timestamp(), &src_data);
let mut buf: [u8; 32] = [0; 32];
let ser_len = hk_reply
.write_to_bytes(&mut buf)
@ -520,8 +535,8 @@ mod tests {
#[test]
fn test_setters() {
let time_stamp = dummy_time_stamp();
let mut pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let mut pus_tm = base_ping_reply_full_ctor(&timestamp);
pus_tm.set_sc_time_ref_status(0b1010);
pus_tm.set_dest_id(0x7fff);
pus_tm.set_msg_counter(0x1f1f);
@ -534,8 +549,8 @@ mod tests {
#[test]
fn test_deserialization_no_source_data() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut buf: [u8; 32] = [0; 32];
let ser_len = pus_tm
.write_to_bytes(&mut buf)
@ -543,13 +558,13 @@ mod tests {
assert_eq!(ser_len, 22);
let (tm_deserialized, size) = PusTm::from_bytes(&buf, 7).expect("Deserialization failed");
assert_eq!(ser_len, size);
verify_ping_reply(&tm_deserialized, false, 22, dummy_time_stamp());
verify_ping_reply(&tm_deserialized, false, 22, dummy_timestamp());
}
#[test]
fn test_manual_field_update() {
let mut sph = SpHeader::tm(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_time_stamp());
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, dummy_timestamp());
let mut tm = PusTm::new(&mut sph, tc_header, None, false);
tm.calc_crc_on_serialization = false;
assert_eq!(tm.data_len(), 0x00);
@ -569,8 +584,8 @@ mod tests {
#[test]
fn test_target_buf_too_small() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut buf: [u8; 16] = [0; 16];
let res = pus_tm.write_to_bytes(&mut buf);
assert!(res.is_err());
@ -593,8 +608,8 @@ mod tests {
#[test]
#[cfg(feature = "alloc")]
fn test_append_to_vec() {
let time_stamp = dummy_time_stamp();
let pus_tm = base_ping_reply_full_ctor(&time_stamp);
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(&timestamp);
let mut vec = Vec::new();
let res = pus_tm.append_to_vec(&mut vec);
assert!(res.is_ok());
@ -606,7 +621,7 @@ mod tests {
#[cfg(feature = "alloc")]
fn test_append_to_vec_with_src_data() {
let src_data = [1, 2, 3];
let hk_reply = base_hk_reply(dummy_time_stamp(), &src_data);
let hk_reply = base_hk_reply(dummy_timestamp(), &src_data);
let mut vec = Vec::new();
vec.push(4);
let res = hk_reply.append_to_vec(&mut vec);
@ -635,7 +650,7 @@ mod tests {
assert_eq!(buf[11], 0x00);
assert_eq!(buf[12], 0x00);
// Timestamp
assert_eq!(&buf[13..20], dummy_time_stamp());
assert_eq!(&buf[13..20], dummy_timestamp());
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[0..20]);
let crc16 = digest.finalize();
@ -647,14 +662,14 @@ mod tests {
tm: &PusTm,
has_user_data: bool,
exp_full_len: usize,
exp_time_stamp: &[u8],
exp_timestamp: &[u8],
) {
assert!(tm.is_tm());
assert_eq!(PusPacket::service(tm), 17);
assert_eq!(PusPacket::subservice(tm), 2);
assert!(tm.sec_header_flag());
assert_eq!(tm.len_packed(), exp_full_len);
assert_eq!(tm.time_stamp(), exp_time_stamp);
assert_eq!(tm.timestamp().unwrap(), exp_timestamp);
if has_user_data {
assert!(!tm.user_data().is_none());
}