Compare commits

..

199 Commits

Author SHA1 Message Date
muellerr d1e8015ded Merge pull request 'bump bitbybit dependency' (#212) from bump-bitbybit-dep into main
Reviewed-on: #212
2026-05-10 23:52:32 +02:00
Robin Mueller e48df492f0 bump bitbybit dependency 2026-05-10 23:52:19 +02:00
muellerr e83bb26f00 Merge pull request 'check in Cargo.lock file' (#211) from check-in-cargo-lock into main
Reviewed-on: #211
2026-05-10 23:51:05 +02:00
Robin Mueller 03cb93a874 check in Cargo.lock file 2026-05-10 23:50:34 +02:00
muellerr 215f663247 Merge pull request 'clippy fix' (#210) from clippy-fix into main
Reviewed-on: http://localhost:3000/rust/spacepackets/pulls/210
2026-05-04 22:44:24 +02:00
Robin Mueller 2342f87823 clippy fix 2026-05-04 22:42:00 +02:00
muellerr 8c5f1539f8 Merge pull request 'fix: unexpected panic in PusTmReader constructor' (#209) from merge-changes into main
Reviewed-on: http://localhost:3000/rust/spacepackets/pulls/209
2026-05-04 22:32:21 +02:00
Robin Mueller bc653685b1 Merge pull request #12 from koloiyolo/fix/unwrap_in_pus_tm_reader_constructor
fix: unexpected panic in PusTmReader constructor
2026-05-04 22:04:27 +02:00
Jakub Kołodziej d9453d46fe fix: unexpected panic in PusTmReader constructor 2026-05-04 11:32:32 +02:00
muellerr 52d45384ab Merge pull request 'clippy fix' (#208) from clippy-fix into main
Reviewed-on: #208
2026-03-09 10:18:18 +01:00
Robin Mueller 8374fddd13 clippy fix 2026-03-09 10:16:52 +01:00
muellerr 55b547ada4 Merge pull request 'patch some missing USLP protocol IDs' (#207) from patch-missing-uslp-protocol-ids into main
Reviewed-on: #207
2026-03-09 10:11:22 +01:00
Robin Mueller ff8fb65151 patch some missing USLP protocol IDs 2026-03-09 10:09:18 +01:00
muellerr a27d8b3634 Merge pull request 'Add CCSDS zero copy writer' (#206) from ccsds-zero-copy-writer into main
Reviewed-on: #206
2026-01-19 09:40:39 +01:00
Robin Mueller 1d971cc4d8 Add CCSDS zero copy writer 2026-01-16 15:10:43 +01:00
muellerr 547a093f28 Merge pull request 'creator with reserved data field' (#205) from add-creator-with-reserved-data-field into main
Reviewed-on: #205
2025-12-10 23:35:58 +01:00
Robin Mueller b9d18ae0a3 add creator with reserved datafield 2025-12-10 23:33:07 +01:00
muellerr 64d53ea48e Merge pull request 'infallible ctor for primary header' (#204) from infallible-primary-header-ctor into main
Reviewed-on: #204
2025-12-10 23:28:38 +01:00
Robin Mueller c07144a193 infallible ctor for primary header 2025-12-10 23:27:56 +01:00
muellerr 5db91e608f Merge pull request 'USLP reader getter functions' (#203) from additional-uslp-getter-functions into main
Reviewed-on: #203
2025-12-10 11:41:27 +01:00
Robin Mueller 369b03f279 USLP reader getter functions 2025-12-10 11:34:28 +01:00
muellerr 66201a6f6d Merge pull request 'USLP raw buffer caching' (#202) from uslp-raw-buffer-caching into main
Reviewed-on: #202
2025-12-10 10:20:04 +01:00
Robin Mueller bb2258e177 USLP raw buffer caching 2025-12-10 10:19:32 +01:00
muellerr 99aa85c701 Merge pull request 'mutability fix' (#201) from vec-ctors-uslp into main
Reviewed-on: #201
2025-12-10 10:18:59 +01:00
Robin Mueller 7f6bbc27e5 mutability fix 2025-12-10 10:05:18 +01:00
muellerr 5eab255562 Merge pull request 'add to_vec writers for USLP' (#200) from vec-ctors-uslp into main
Reviewed-on: #200
2025-12-10 10:00:22 +01:00
Robin Mueller 337007f006 add to_vec writers for USLP 2025-12-10 09:59:52 +01:00
muellerr 6e2b4c87db Merge pull request 'minor tweaks' (#199) from missing-uslp-ctor into main
Reviewed-on: #199
2025-12-10 09:41:55 +01:00
Robin Mueller f5393aabe0 minor tweaks 2025-12-10 09:41:43 +01:00
muellerr adcf62833d Merge pull request 'added missing ctor' (#198) from missing-uslp-ctor into main
Reviewed-on: #198
2025-12-10 09:39:46 +01:00
Robin Mueller 42f87dad99 added missing ctor 2025-12-09 15:18:02 +01:00
muellerr e5b165def3 Merge pull request 'Add USLP frame creator' (#197) from add-uslp-frame-creator into main
Reviewed-on: #197
2025-12-09 15:02:02 +01:00
Robin Mueller 9882d3cdc1 add tests 2025-12-09 15:00:52 +01:00
Robin Mueller d8fb5e1ca5 add USLP frame creator 2025-12-08 20:49:17 +01:00
muellerr 9035ecd139 Merge pull request 'USLP type improvements' (#196) from uslp-type-improvements into main
Reviewed-on: #196
2025-12-08 18:53:48 +01:00
Robin Mueller 2a3e280d96 USLP type improvements 2025-12-08 18:52:06 +01:00
muellerr cf9306d992 Merge pull request 'more precise error handling for CCSDS support' (#195) from more-precise-error-handling into main
Reviewed-on: #195
2025-11-27 17:36:37 +01:00
Robin Mueller 73b11d0ae2 more precise error handling for CCSDS support 2025-11-27 17:34:51 +01:00
muellerr b7efa0378d Merge pull request 'fix docs CI' (#194) from fix-docs-ci into main
Reviewed-on: #194
2025-11-27 12:47:56 +01:00
Robin Mueller c6d10422d5 fix docs CI 2025-11-27 12:44:26 +01:00
muellerr 416a89b807 Merge pull request 'added raw data getter function' (#193) from ccsds-raw-data-getter into main
Reviewed-on: #193
2025-11-19 17:39:17 +01:00
Robin Mueller 8136f554b5 added raw data getter function 2025-11-19 17:28:11 +01:00
muellerr 665cb3b107 Merge pull request 'sequence counter update' (#192) from seq-counter-update into main
Reviewed-on: #192
2025-11-19 17:11:11 +01:00
Robin Mueller 989ace786e Update sequence counter impl 2025-11-19 17:10:02 +01:00
muellerr 402331c725 Merge pull request 'added distinction between CCSDS packet and user data' (#191) from ccsds-reader-user-data into main
Reviewed-on: #191
2025-11-19 17:02:33 +01:00
muellerr 3e3f33a7d1 added distiction between CCSDS packet and user data 2025-11-19 16:43:39 +01:00
muellerr f001234025 Merge pull request 'clippy' (#190) from clippy into main
Reviewed-on: #190
2025-11-06 13:43:48 +01:00
Robin Mueller e5a7839901 clippy 2025-11-06 13:43:20 +01:00
muellerr 5707c6322a Merge pull request 'changelog' (#189) from prep-v0.17.0 into main
Reviewed-on: #189
2025-11-06 13:27:09 +01:00
Robin Mueller 093f82ae86 changelog 2025-11-06 13:26:46 +01:00
muellerr e68d1ade48 Merge pull request 'Finish full crate docs' (#188) from renaming-docs-for-ecss into main
Reviewed-on: #188
2025-11-05 20:20:35 +01:00
Robin Mueller fbdc325d0d Finish full crate docs 2025-11-05 20:18:31 +01:00
muellerr 4bc0219cb2 Merge pull request 'docs and minor cfdp change' (#187) from cfdp-update-docs into main
Reviewed-on: #187
2025-11-04 18:54:33 +01:00
muellerr 3f4f76849f docs and minor cfdp change 2025-11-04 18:50:53 +01:00
muellerr fb1e2fc583 Merge pull request 'added missing derives' (#186) from add-missing-derives into main
Reviewed-on: #186
2025-11-04 15:57:46 +01:00
Robin Mueller 96e5851864 added missing derives 2025-11-04 15:57:09 +01:00
muellerr b4d00c26c5 Merge pull request 'add direct APID getter' (#185) from add-direct-ccsds-apid-getter into main
Reviewed-on: #185
2025-11-04 15:40:10 +01:00
Robin Mueller a8b64f2fef add direct APID getter 2025-11-04 15:39:37 +01:00
muellerr e7cb6f2a7a Merge pull request 'error reporting bugfix' (#184) from ccsds-packet-reader-error-reporting-fix into main
Reviewed-on: #184
2025-11-04 15:34:54 +01:00
Robin Mueller 973ba4d3c4 error reporting bugfix 2025-11-04 15:34:15 +01:00
muellerr 8789e34c14 Merge pull request 'added missing function to reader' (#183) from add-ccsds-id-function-to-reader into main
Reviewed-on: #183
2025-10-31 16:56:12 +01:00
Robin Mueller a68e82a825 added missing function to reader 2025-10-31 16:55:45 +01:00
muellerr 0b46fa785b Merge pull request 'better naming' (#182) from naming-improvement into main
Reviewed-on: #182
2025-10-31 16:03:40 +01:00
Robin Mueller c57ee3e131 better naming 2025-10-31 16:03:08 +01:00
muellerr 6ac84c3dca Merge pull request 'add option to ignore checksum for CCSDS' (#181) from add-option-to-ignore-checksum into main
Reviewed-on: #181
2025-10-31 16:01:11 +01:00
Robin Mueller 374f39f13b add option to ignore checksum for CCSDS 2025-10-31 15:59:41 +01:00
muellerr 2bc6167710 Merge pull request 'less confusing naming' (#180) from less-confusing-naming into main
Reviewed-on: #180
2025-10-31 12:55:53 +01:00
Robin Mueller cfe0937afe less confusing naming 2025-10-31 12:55:07 +01:00
muellerr e1c693cb29 Merge pull request 'update ECSS PUS naming convention' (#179) from update-ecss-pus-naming-convention into main
Reviewed-on: #179
2025-10-31 12:39:39 +01:00
Robin Mueller 38165420b7 update ECSS PUS naming convention 2025-10-31 12:38:59 +01:00
muellerr 0d09ff7825 Merge pull request 'add docs and minor changes' (#178) from add-docs-minor-changes into main
Reviewed-on: #178
2025-10-31 11:45:06 +01:00
Robin Mueller 8f2096ca35 add docs and minor changes 2025-10-31 11:39:23 +01:00
muellerr 3f35e9dba9 Merge pull request 'add owned CCSDS packet creator' (#177) from add-owned-ccsds-packet-creator into main
Reviewed-on: #177
2025-10-31 10:10:50 +01:00
Robin Mueller ea96099f55 add owned CCSDS packet creator 2025-10-31 10:10:11 +01:00
muellerr e117239852 Merge pull request 'add useful functions' (#176) from add-ccsds-id-functions into main
Reviewed-on: #176
2025-10-30 18:58:27 +01:00
Robin Mueller 844c517a94 add useful functions 2025-10-30 18:58:10 +01:00
muellerr 0ae2ac149b Merge pull request 'add CCSDS packet ID' (#175) from add-ccsds-packet-id into main
Reviewed-on: #175
2025-10-29 21:58:56 +01:00
Robin Mueller 2b41f9754d add CCSDS packet ID 2025-10-29 21:45:07 +01:00
muellerr 8e2e0ce632 Merge pull request 'fix portable atomic support' (#174) from fix-portable-atomic-support into main
Reviewed-on: #174
2025-10-29 16:13:09 +01:00
Robin Mueller 14d935ac2a fix portable atomic support 2025-10-29 16:05:11 +01:00
muellerr 756a803213 Merge pull request 'prepare v0.17.0' (#173) from prepare-v0.17.0 into main
Reviewed-on: #173
2025-10-29 16:04:19 +01:00
Robin Mueller 937bdeaf54 prepare v0.17.0 2025-10-29 15:48:29 +01:00
muellerr bc30143d61 Merge pull request 'start adding improved CCSDS packet support' (#172) from add-better-ccsds-packet-support into main
Reviewed-on: #172
2025-10-29 15:44:38 +01:00
Robin Mueller 549e323211 start adding improved CCSDS packet support 2025-10-29 15:28:25 +01:00
muellerr 82c3e06ac0 Merge pull request 'feature gate all core atomics' (#171) from feature-gate-all-core-atomics into main
Reviewed-on: #171
2025-10-29 11:26:57 +01:00
Robin Mueller 750add26ef feature gate all core atomics 2025-10-29 11:24:05 +01:00
muellerr c3ff947fb0 Merge pull request 'move some modules' (#170) from clean-up-cds-time-mod into main
Reviewed-on: #170
2025-10-15 15:26:39 +02:00
Robin Mueller 8d86ecc8ee move some modules 2025-10-15 15:26:06 +02:00
muellerr 4b2bebb8cb Merge pull request 'simplified CDS short impl' (#169) from simplify-cds-timestamp-impl into main
Reviewed-on: #169
2025-10-15 15:01:06 +02:00
Robin Mueller e0b7a6a6bb simplified CDS short impl 2025-10-15 11:57:44 +02:00
muellerr 49983a5d6c Merge pull request 'update for docs generation' (#168) from doc-generation-update into main
Reviewed-on: #168
2025-10-02 09:45:45 +02:00
Robin Mueller 04c864d6a2 update for docs generation 2025-10-01 00:20:47 +02:00
muellerr 922801cc74 Merge pull request 'try to fix CI' (#167) from ci-fix into main
Reviewed-on: #167
2025-09-26 15:14:20 +02:00
Robin Mueller f5717d98cd try to fix CI 2025-09-26 15:10:27 +02:00
muellerr 6ea7b8902a Merge pull request 'SpHeader::packet_len is pub now' (#166) from sp-packet-len-pub into main
Reviewed-on: #166
2025-09-26 15:09:07 +02:00
Robin Mueller f6ac9ee918 SpHeader::packet_len is pub now 2025-09-26 15:08:29 +02:00
muellerr 0aa41fee92 Merge pull request 'prepare v0.16.0' (#165) from prep-v0.16.0 into main
Reviewed-on: #165
2025-09-24 19:58:02 +02:00
Robin Mueller d1516d669d prepare v0.16.0 2025-09-24 19:56:52 +02:00
muellerr b1ebb4d7c4 Merge pull request 'update docs on coverage' (#164) from update-coverage-docs into main
Reviewed-on: #164
2025-09-24 19:55:47 +02:00
Robin Mueller cd79af4440 update docs on coverage 2025-09-24 19:54:45 +02:00
muellerr 6a760c8585 Merge pull request 'improve backwards compatibility' (#163) from improve-backwards-compat into main
Reviewed-on: #163
2025-09-24 19:54:11 +02:00
Robin Mueller 5eb409f1ec improve backwards compatibility 2025-09-24 19:49:51 +02:00
muellerr 69d416d6ff Merge pull request 'improvement for NAK API' (#162) from nak-api-improvement into main
Reviewed-on: #162
2025-09-23 17:08:15 +02:00
Robin Mueller e2b239ae61 improvement for NAK API 2025-09-23 17:06:45 +02:00
muellerr b06d7c1a87 Merge pull request 'better error handling' (#161) from better-nak-error into main
Reviewed-on: #161
2025-09-18 17:37:00 +02:00
Robin Mueller ec1ddbde81 better error handling 2025-09-18 17:36:51 +02:00
muellerr 7f4ada1734 Merge pull request 'NAK constructor is pub' (#160) from nak-new-pub into main
Reviewed-on: #160
2025-09-18 17:35:48 +02:00
Robin Mueller 15f97e960b NAK constructor is pub 2025-09-18 17:32:11 +02:00
muellerr 49b7c2d072 Merge pull request 'PDU header improvements' (#159) from pdu-header-improvements into main
Reviewed-on: #159
2025-09-18 16:56:26 +02:00
muellerr 1ed23bd7ef PDU header improvements 2025-09-18 16:54:28 +02:00
muellerr a82cdb1e82 Merge pull request 'nak docs' (#158) from nak-docs into main
Reviewed-on: #158
2025-09-17 13:42:04 +02:00
muellerr 12e7062075 nak docs 2025-09-17 13:40:49 +02:00
muellerr a1e40834f5 Merge pull request 'improve ACK PDU' (#157) from improve-ack-pdu into main
Reviewed-on: #157
2025-09-15 13:02:30 +02:00
Robin Mueller 3f6a5df8e7 improve ACK PDU 2025-09-15 13:02:16 +02:00
muellerr a8d5fdf8d3 Merge pull request 'extend NAK PDU' (#156) from extend-nak-pdu into main
Reviewed-on: #156
2025-09-15 10:30:02 +02:00
Robin Mueller 62326da276 extend NAK PDU 2025-09-15 10:16:07 +02:00
muellerr 477890346a Merge pull request 'improve CFDP module' (#154) from cfdp-module-improvements into main
Reviewed-on: #154
2025-09-11 16:10:47 +02:00
Robin Mueller 9394beea38 improve CFDP module 2025-09-11 16:03:58 +02:00
muellerr 6c425e137a Merge pull request 'add coverage to justfile' (#155) from update-justfile into main
Reviewed-on: #155
2025-09-11 16:03:41 +02:00
muellerr 24b91a7a83 add coverage to justfile 2025-09-11 13:22:27 +02:00
muellerr a7c6ce7d44 Merge pull request 'improve CFDP module' (#153) from cfdp-module-improvements into main
Reviewed-on: #153
2025-09-11 09:12:59 +02:00
Robin Mueller c68e71a25e improve CFDP module 2025-09-11 09:09:41 +02:00
muellerr 272a961a70 Merge pull request 'add packet_len direct method for SpHeader' (#152) from sp-header-tweak into main
Reviewed-on: #152
2025-09-10 21:05:56 +02:00
Robin Mueller 6f4df7e3c2 add packet_len direct method for SpHeader 2025-09-10 19:04:47 +02:00
muellerr 15c477e810 Merge pull request 'prepare v0.16.0' (#151) from prep-v0.16.0 into main
Reviewed-on: #151
2025-09-10 18:08:10 +02:00
Robin Mueller e5b10920a0 prepare v0.16.0 2025-09-10 18:03:35 +02:00
muellerr 3f8434e1fa Merge pull request 'add missing Error impls' (#150) from add-missing-error-impls into main
Reviewed-on: #150
2025-09-10 17:54:46 +02:00
Robin Mueller ec3f462931 add missing Error impls 2025-09-10 17:52:49 +02:00
muellerr e6686caba1 Merge pull request 'add-missing-defmt-impls' (#149) from add-missing-defmt-impls into main
Reviewed-on: #149
2025-09-10 17:52:39 +02:00
Robin Mueller 2a0b21983e add some missing defmt impls 2025-09-10 17:48:49 +02:00
muellerr 4e153e0b68 Merge pull request 'Add TM builder API' (#148) from add-tm-builder-api into main
Reviewed-on: #148
2025-09-10 17:39:05 +02:00
Robin Mueller aaac15e3d0 Add TM builder API 2025-09-10 17:36:39 +02:00
muellerr 89788c1341 Merge pull request 'add first builder API' (#147) from add-tc-builder-api into main
Reviewed-on: #147
2025-09-10 16:38:25 +02:00
Robin Mueller 578be2da8f add first TC builder API 2025-09-10 16:12:06 +02:00
muellerr 3a21daf8de Merge pull request 'refactor and improve ECSS module' (#146) from refactor-improve-ecss-module into main
Reviewed-on: #146
2025-09-10 15:37:27 +02:00
Robin Mueller 8fd46f6a30 refactor and improve ECSS module 2025-09-10 15:28:58 +02:00
muellerr c6b74fecbd Merge pull request 'start making ECSS checksum optional' (#144) from ecss-checksum-optional into main
Reviewed-on: #144
2025-09-09 16:14:45 +02:00
Robin Mueller 60e35559e5 start making ECSS checksum optional 2025-09-09 16:14:11 +02:00
muellerr e708f1b861 Merge pull request 'some more tests' (#145) from add-some-more-tests into main
Reviewed-on: #145
2025-09-09 15:57:11 +02:00
Robin Mueller 91490b5dd6 some more tests 2025-09-09 15:56:44 +02:00
muellerr e151b8e761 Merge pull request 'fix for embedded systems, introduce portable atomic seq counters' (#143) from portable-atomic-seq-counters-embedded-fix into main
Reviewed-on: #143
2025-09-09 13:49:23 +02:00
Robin Mueller 2839174e5f fix for embedded systems, introduce portable atomic seq counters 2025-09-09 13:34:12 +02:00
muellerr 6e2db87fa9 Merge pull request 'improve sequence counters' (#141) from improve-seq-counters into main
Reviewed-on: #141
2025-09-09 11:53:31 +02:00
Robin Mueller e8a01dc6b2 improve sequence counters 2025-09-09 11:51:59 +02:00
muellerr 20403bda32 Merge pull request 'sequence counter improvements' (#140) from seq-counter-improvements into main
Reviewed-on: #140
2025-09-09 10:27:08 +02:00
Robin Mueller 2cbd48331c sequence counter improvements 2025-09-09 10:24:20 +02:00
muellerr c1346f2b12 Merge pull request 'add some more tests' (#138) from some-more-tests into main
Reviewed-on: #138
2025-09-08 17:01:45 +02:00
muellerr 2e3a7849a7 add some more tests 2025-09-08 16:59:41 +02:00
muellerr 86ebea8eb8 Merge pull request 'Add basic USLP support' (#137) from add-basic-uslp-support into main
Reviewed-on: #137
2025-09-08 16:59:21 +02:00
muellerr 2c8c77acb8 add basic USLP support 2025-09-08 16:51:33 +02:00
muellerr 63d74aa58b Merge pull request 'PUS version fixes' (#136) from small-bugfix-pus-tm-a into main
Reviewed-on: #136
2025-08-26 16:41:13 +02:00
muellerr 5a86f89c83 version fixes 2025-08-26 16:40:44 +02:00
muellerr b8ae26c302 Merge pull request 'improvement for naming' (#135) from naming-improvement into main
Reviewed-on: #135
2025-08-26 16:22:19 +02:00
muellerr 160b1dedf9 improvement for naming 2025-08-26 16:16:54 +02:00
muellerr 8eccf1fa29 Merge pull request 'NAK PDU reader update' (#134) from nak-pdu-reader-refactoring into main
Reviewed-on: #134
2025-08-20 17:53:32 +02:00
Robin Mueller 8445b7cc31 NAK PDU reader update 2025-08-20 16:02:08 +02:00
muellerr a2971f8f73 Merge pull request 'add badge' (#133) from add-chat-badge into main
Reviewed-on: #133
2025-08-14 14:22:09 +02:00
Robin Mueller ba3b66326d add badge 2025-08-14 14:21:37 +02:00
muellerr de2675e602 Merge pull request 'add PUS A support' (#132) from add-pus-a-support into main
Reviewed-on: #132
2025-08-13 17:24:50 +02:00
Robin Mueller 3d344c11cc add PUS A support 2025-08-13 17:04:39 +02:00
muellerr 6e2c35e0c0 Merge pull request 'prepare next release' (#131) from prep-v0.15.0 into main
Reviewed-on: #131
2025-07-18 19:32:28 +02:00
Robin Mueller 026e1a50b9 prepare next release 2025-07-18 19:31:55 +02:00
muellerr 440b836b70 Merge pull request 'allow arbitrary crc minor version' (#130) from allow-arbitrary-crc-minor-version into main
Reviewed-on: #130
2025-07-18 19:28:31 +02:00
Robin Mueller 00e28e4a96 allow arbitrary crc minor version 2025-07-18 19:27:59 +02:00
muellerr 4c1cad5b72 Merge pull request 'reserved data variants for ECSS TM and TC' (#129) from ecss-tm-tc-reserved-data-variants into main
Reviewed-on: #129
2025-05-16 19:06:08 +02:00
muellerr 5cd5c1ce6d reserved data variants for ECSS TM and TC 2025-05-16 19:04:23 +02:00
muellerr de99bb926a Merge pull request 'small changelog tweak' (#128) from small-changelog-tweak into main
Reviewed-on: #128
2025-05-10 15:08:18 +02:00
muellerr 167f53cac7 small changelog tweak 2025-05-10 15:07:58 +02:00
muellerr 172227b843 Merge pull request 'update MSRV check' (#127) from update-msrv-check into main
Reviewed-on: #127
2025-05-10 15:04:21 +02:00
muellerr 1bbca6866b update MSRV check 2025-05-10 15:03:05 +02:00
muellerr b569208d45 Merge pull request 'prepare v0.14.0' (#126) from prepare-release into main
Reviewed-on: #126
2025-05-10 14:58:45 +02:00
muellerr d9709ffd6c prepare v0.14.0 2025-05-10 14:54:27 +02:00
muellerr 243dc64a78 Merge pull request 'remove badge' (#125) from remove-badge into main
Reviewed-on: #125
2025-05-10 14:38:23 +02:00
muellerr a6dc173f7f Merge branch 'main' into remove-badge 2025-05-10 14:38:19 +02:00
muellerr 86dddbeef5 remove badge 2025-05-10 14:36:00 +02:00
muellerr 17d112e838 Merge pull request 'one more test fix' (#124) from one-more-test-fix into main
Reviewed-on: #124
2025-05-10 14:31:53 +02:00
muellerr 9c8467ccfe one more test fix 2025-05-10 14:30:00 +02:00
muellerr 217a8c2cc7 Merge pull request 'formatting' (#123) from formatting into main
Reviewed-on: #123
2025-05-10 14:26:27 +02:00
muellerr 349e34bed6 formatting 2025-05-10 14:25:44 +02:00
muellerr d6a76ca360 Merge pull request 'CRC handling and dependency update' (#122) from msp430-tweak into main
Reviewed-on: #122
2025-05-10 14:24:54 +02:00
muellerr 8f4351771b API variants which use table-less CRC 2025-05-10 13:58:10 +02:00
muellerr b08c3329f4 Merge pull request 'bump patch release' (#120) from prep-v0.13.1 into main
Reviewed-on: #120
2025-03-21 14:53:01 +01:00
muellerr 08e0d39154 bump patch release 2025-03-21 14:50:10 +01:00
muellerr ab97607024 Merge pull request 'clippy fixes' (#119) from clippy-fixes into main
Reviewed-on: #119
2025-03-21 14:47:06 +01:00
muellerr 60d1f77844 bugfix due to operator precendence and clippy fixes 2025-03-21 14:46:13 +01:00
muellerr 5a112b7f39 Merge pull request 'add funding file' (#118) from add-funding-file into main
Reviewed-on: #118
2025-03-17 16:33:46 +01:00
muellerr e774dd69d4 add funding file 2025-03-17 16:32:43 +01:00
muellerr a03d26a49c Merge pull request 'prep v0.13.0' (#117) from prep-v0.13.0 into main
Reviewed-on: #117
2024-11-08 16:55:52 +01:00
muellerr 026173514f prep v0.13.0 2024-11-08 16:54:53 +01:00
muellerr 2d7ccc0909 Merge pull request 'Add back API which was deleted accidently' (#116) from add-back-api into main
Reviewed-on: #116
2024-11-08 15:50:46 +01:00
muellerr 05d3bac927 Add back API which was deleted accidently 2024-11-08 15:46:42 +01:00
muellerr d58df5fee2 Merge pull request 'Switch to thiserror' (#115) from switch-to-thiserror into main
Reviewed-on: #115
2024-11-08 15:42:37 +01:00
muellerr 9d23ac5b9b switch to thiserror completely 2024-11-08 15:26:40 +01:00
muellerr c0b4653c01 Merge pull request 'bump CI msrv check' (#114) from bump-msrv-check into main
Reviewed-on: #114
2024-11-08 11:27:52 +01:00
muellerr f156833985 bump CI msrv check 2024-11-08 11:26:51 +01:00
muellerr 9aea3dba00 Merge pull request 'bump dependencies' (#113) from bump-dependencies into main
Reviewed-on: #113
2024-11-08 11:14:04 +01:00
muellerr 48247a0a87 bump thiserror and zerocopy 2024-11-08 11:13:41 +01:00
muellerr f70b957d9a Merge pull request 'docs fixes' (#112) from smaller-doc-fixes into main
Reviewed-on: #112
2024-11-07 23:28:41 +01:00
muellerr fbf953df0e docs fixes 2024-11-04 11:42:51 +01:00
38 changed files with 13926 additions and 2760 deletions
+8 -3
View File
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.70.0 - uses: dtolnay/rust-toolchain@1.83
- run: cargo check --release - run: cargo check --release
cross-check: cross-check:
@@ -39,12 +39,13 @@ jobs:
matrix: matrix:
target: target:
- armv7-unknown-linux-gnueabihf - armv7-unknown-linux-gnueabihf
- thumbv6m-none-eabi
- thumbv7em-none-eabihf - thumbv7em-none-eabihf
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf" targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf, thumbv6m-none-eabi"
- run: cargo check --release --target=${{matrix.target}} --no-default-features - run: cargo check --release --target=${{matrix.target}} --no-default-features
fmt: fmt:
@@ -53,6 +54,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check - run: cargo fmt --all -- --check
docs: docs:
@@ -61,7 +64,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --all-features --no-deps
clippy: clippy:
name: Clippy name: Clippy
@@ -69,4 +72,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings - run: cargo clippy -- -D warnings
-1
View File
@@ -1,6 +1,5 @@
# Rust # Rust
/target /target
/Cargo.lock
# CLion # CLion
/.idea/* /.idea/*
+161
View File
@@ -8,6 +8,157 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.18.0] ?
## Changed
- Added distinction between `CcsdsPacketReader::user_data` and `CcsdsPacketReader::packet_data`.
- Added distinction between `CcsdsPacketCreatorWithReservedData::user_data` and
`CcsdsPacketCreatorWithReservedData::packet_data`, including mutable variants as well.
- `SequenceCounter::MAX_BIT_WIDTH` is now a regular trait method `SequenceCounter::max_bit_width`
to allow dyn compatibility and easier usage in trait objects.
- Improved type level support in USLP module by using VC ID type `u6` and MAP ID type `u4`.
## Added
- `checksum` getter for `CcsdsPacketReader`.
- Added `SequenceCounterOnFile` which persists the sequence counter by writing it to a file.
- Added `SequenceCounter::set` method which allows manually setting an initial value.
- Added `CcsdsPacketReader::raw_data` full data getter.
- `UslpFrameCreator` constructor
- Additional reader methods on `UslpFrameReader`
## Removed
- `SequenceCounter::increment_mut` and `SequenceCounter::get_and_increment_mut`
# [v0.17.0] 2025-11-06
## Changed
- `CdsCommon` renamed to `CdsBase`
- cfdp: Removed `FileDirectiveType` variant `*Pdu` suffix
- ecss: Renamed `Subservice` to `MessageSubtypeId`
- Simplified CDS short timestamp, contains one less field which reduced serialization length.
- Renamed `UnsignedEnum::value` to `UnsignedEnum::value_raw`, `value` is reserved for the `const`
value getter.
- Renamed `CcsdsPrimaryHeader::from_composite_fields` to
`CcsdsPrimaryHeader::new_from_composite_fields`
- Renamed `PusPacket::service` to `PusPacket::service_type_id` and `PusPacket::subservice` to
`PusPacket::message_subtype_id`. Also added `PusPacket::message_type_id`. Performed the same
change for the ECSS PUS C secondary header traits.
## Added
- Added `CcsdsPacketCreator`, `CcsdsPacketReader`, `CcsdsPacketCreatorWithReservedData` and
`CcsdsPacketCreatorOwned` which simplify the process of creating full CCSDS space packets.
- Added new optional `portable-atomic` because portable atomics might not work on every
architecture in addition to requiring atomic CAS support enabled inside for the crate.
## Fixed
- All `core::sync::Atomic?` usages are feature gated properly to allow compilation on systems
without atomic CAS.
# [v0.16.1] 2025-09-26
## Fixed
`SpHeader::packet_len` is public now.
# [v0.16.0] 2025-09-24
- Bump Rust MSRV to v1.83
## Changed
- `PusTcCreator` has its own `service`, `subservice` and `apid` methods and does not require trait
imports anymore.
- CFDP NAK PDU `SegmentRequestIter` is not generic over the file size anymore. Instead, the
iterator returns pairs of `u64` for both large and normal file size.
- `PusVersion::VersionNotSupported` contains raw version number instead of `PusVersion` enum now
to make it more flexible.
- `pus_version` API now returns a `Result<PusVersion, u8>` instead of a `PusVersion` to allow
modelling invalid version numbers properly.
- Renamed `CcsdsPacket::total_len` to `CcsdsPacket::packet_len`
- Renamed `SequenceCountProvider` to `SequenceCounter`
- Renamed `SeqCountProviderSimple` to `SequenceCounterSimple`
- Renamed `CcsdsSimpleSeqCountProvider` to `SequenceCounterCcsdsSimple`
- Renamed `SeqCountProviderSync` to `SequenceCounterSync`
- Renamed `PusPacket::opt_crc16` to `PusPacket::checksum`
- Renamed `PacketSequenceCtrl` to `PacketSequenceControl`
- ECSS checksum generation is now optional as specified in the standard. Added `has_checksum`
parameters for ECSS TM/TC creators and readers to reflect this.
- APID is represented by `arbitrary-int::u11` while the sequence count is represented by
`arbitrary-int::u14`. A lot of corresponding checks were removed because the type now ensure
value validity.
- ACK field changed from `u8` to `AckFlags` structure.
- PUS version raw representation is `u4` now.
- SC time reference status representation is `u4` now.
- Renamed `ptype` to `packet_type`
- Renamed `PduHeader::new_no_file_data` to `PduHeader::new_for_file_directive`
- Renamd `FinishedPduCreator::new_generic` to `new` and `new_default` to `new_no_error`
## Removed
- `PusVersion::Invalid`, which will be modelled with `Result<PusVersion, u8>` now.
## Added
- `cfdp::pdu::ack::InvalidAckedDirectiveCodeError` which is returned by the `AckPdu` constructor.
- `cfdp::pdu::nak::NakPduCreatorWithReservedSegReqsBuf` constructor which exposes the segment
request buffer mutably to avoid the need for a separate segment request buffer.
- `SpHeader::packet_len` direct method.
- `AckFlags` which is implemented with `bitbybit::bitfield`
- `ApidOutOfRangeError` and `SequenceCountOutOfRangeError`
- Added PUS A legacy support for telecommands inside the `ecss.tc_pus_a` module
- Added `SequenceCounter::increment_mut` and `SequenceCounter::get_and_increment_mut`
- Implemented `SequenceCounter` for `Atomic` unsigned types and references of them
- `PusPacket::has_checksum` and `WritablePusPacket::has_checksum`
- PUS TC builder API, either via `PusTcBuilder::new`, or `PusTcCreator::builder`
# [v0.15.0] 2025-07-18
## Added
- `PusTcCreatorWithReservedAppData` and `PusTmCreatorWithReservedSourceData` constructor variants
which allow writing source/app data into the serialization buffer directly without
requiring an extra buffer.
# [v0.14.0] 2025-05-10
## Changed
- Moved CRC constants/implementations to dedicated `crc` module.
- `crc::CRC_CCITT_FALSE_NO_TABLE` and `crc::CRC_CCITT_FALSE_BIG_TABLE` variants.
- Renamed `PusPacket::crc16` to `PusPacket::opt_crc16`.
## Added
- `WritablePusPacket::write_to_bytes_crc_no_table` and `WritablePusPacket::write_to_bytes_no_crc`
variants.
- `PusTmReader::new_crc_no_table` and `PusTcReader::new_crc_no_table` variants.
- `crc16` methods for PUS TM and PUS TC reader.
- PUS TM and PUS TC reader now return the reader instance directly instead of a tuple of the reader
and the read size. The instance `total_len` method can be used to retrieve the read lenght.
# [v0.13.1] 2025-03-21
- Bugfix due to operator precendence for `PusTcSecondaryHeader::pus_version`,
`PusTcSecondaryHeaderWithoutTimestamp::pus_version`, `CdsTime::from_bytes_with_u16_days` and
`CdsTime::from_bytes_with_u24_days`
# [v0.13.0] 2024-11-08
- Bumped MSRV to 1.81.0
- Bump `zerocopy` to v0.8.0
- Bump `thiserror` to v2.0.0
## Changed
- Migrated all Error implementations to thiserror, improved some naming and error handling in
general
# [v0.12.0] 2024-09-10 # [v0.12.0] 2024-09-10
- Bumped MSRV to 1.70.0 - Bumped MSRV to 1.70.0
@@ -550,3 +701,13 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta
Initial release with CCSDS Space Packet Primary Header implementation and basic PUS TC and TM Initial release with CCSDS Space Packet Primary Header implementation and basic PUS TC and TM
implementations. implementations.
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.17.0...HEAD
[v0.17.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.16.1...v0.17.0
[v0.16.1]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.16.0...v0.16.1
[v0.16.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.15.0...v0.16.0
[v0.15.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.14.0...v0.15.0
[v0.14.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.1...v0.14.0
[v0.13.1]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.13.0...v0.13.1
[v0.13.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.12.0...v0.13.0
[v0.12.0]: https://egit.irs.uni-stuttgart.de/rust/spacepackets/compare/v0.11.2...v0.12.0
Generated
+784
View File
@@ -0,0 +1,784 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "arbitrary-int"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5"
[[package]]
name = "arbitrary-int"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c858caffa49edfc4ecc45a4bec37abd3e88041a2903816f10f990b7b41abc281"
dependencies = [
"defmt 0.3.100",
"serde",
]
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitbybit"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762"
dependencies = [
"arbitrary-int 1.3.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crc"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "defmt"
version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
dependencies = [
"defmt 1.0.1",
]
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
"bitflags 1.3.2",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
dependencies = [
"defmt-parser",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror",
]
[[package]]
name = "delegate"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "num_enum"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"heapless",
"serde",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "spacepackets"
version = "0.17.0"
dependencies = [
"arbitrary-int 2.0.0",
"bitbybit",
"chrono",
"crc",
"defmt 1.0.1",
"delegate",
"num-traits",
"num_enum",
"paste",
"portable-atomic",
"postcard",
"serde",
"tempfile",
"thiserror",
"time",
"zerocopy",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"num-conv",
"powerfmt",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+24 -47
View File
@@ -1,8 +1,8 @@
[package] [package]
name = "spacepackets" name = "spacepackets"
version = "0.12.0" version = "0.17.0"
edition = "2021" edition = "2021"
rust-version = "1.70.0" rust-version = "1.83"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "Generic implementations for various CCSDS and ECSS packet standards" description = "Generic implementations for various CCSDS and ECSS packet standards"
homepage = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" homepage = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
@@ -14,57 +14,34 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
[dependencies] [dependencies]
crc = "3" crc = "3"
delegate = ">=0.8, <=0.13" delegate = "0.13"
paste = "1" paste = "1"
zerocopy = { version = "0.8", features = ["derive"] }
thiserror = { version = "2", default-features = false }
num_enum = { version = "0.7", default-features = false }
num-traits = { version = "0.2", default-features = false }
serde = { version = "1", optional = true, default-features = false, features = ["derive"] }
arbitrary-int = { version = "2" }
portable-atomic = { version = "1", optional = true }
bitbybit = "2"
[dependencies.zerocopy] time = { version = "0.3", default-features = false, optional = true }
version = "0.7" chrono = { version = "0.4", default-features = false, optional = true }
features = ["derive"] defmt = { version = "1", default-features = false, optional = true }
[dependencies.thiserror]
version = "1"
optional = true
[dependencies.num_enum]
version = ">0.5, <=0.7"
default-features = false
[dependencies.serde]
version = "1"
optional = true
default-features = false
features = ["derive"]
[dependencies.time]
version = "0.3"
default-features = false
optional = true
[dependencies.chrono]
version = "0.4"
default-features = false
optional = true
[dependencies.num-traits]
version = "0.2"
default-features = false
[dependencies.defmt]
version = "0.3"
optional = true
[dev-dependencies]
postcard = "1"
chrono = "0.4"
[features] [features]
default = ["std"] default = ["std"]
std = ["chrono/std", "chrono/clock", "alloc", "thiserror"] std = ["alloc", "chrono/std", "chrono/clock", "thiserror/std"]
serde = ["dep:serde", "chrono/serde"] portable-atomic = ["dep:portable-atomic", "portable-atomic/require-cas"]
alloc = ["postcard/alloc", "chrono/alloc", "defmt/alloc", "serde/alloc"] defmt = ["dep:defmt", "arbitrary-int/defmt"]
chrono = ["dep:chrono"] serde = ["dep:serde", "chrono?/serde", "arbitrary-int/serde"]
alloc = ["chrono?/alloc", "defmt?/alloc", "serde?/alloc"]
timelib = ["dep:time"] timelib = ["dep:time"]
defmt = ["dep:defmt"]
[dev-dependencies]
postcard = { version = "1", features = ["alloc"] }
chrono = "0.4"
tempfile = "3"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
+1
View File
@@ -0,0 +1 @@
github: robamu
+8 -7
View File
@@ -1,7 +1,7 @@
[![Crates.io](https://img.shields.io/crates/v/spacepackets)](https://crates.io/crates/spacepackets) [![Crates.io](https://img.shields.io/crates/v/spacepackets)](https://crates.io/crates/spacepackets)
[![docs.rs](https://img.shields.io/docsrs/spacepackets)](https://docs.rs/spacepackets) [![docs.rs](https://img.shields.io/docsrs/spacepackets)](https://docs.rs/spacepackets)
[![ci](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml) [![ci](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/spacepackets-rs/actions/workflows/ci.yml)
[![coverage](https://shields.io/endpoint?url=https://absatsw.irs.uni-stuttgart.de/projects/spacepackets/coverage-rs/latest/coverage.json)](https://absatsw.irs.uni-stuttgart.de/projects/spacepackets/coverage-rs/latest/index.html) [![matrix chat](https://img.shields.io/matrix/sat-rs%3Amatrix.org)](https://matrix.to/#/#sat-rs:matrix.org)
ECSS and CCSDS Spacepackets ECSS and CCSDS Spacepackets
====== ======
@@ -43,6 +43,9 @@ Currently, this includes the following components:
- [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library. - [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library.
- [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the - [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
[`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types. [`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types.
- [`portable-atomic`](https://github.com/taiki-e/portable-atomic): Basic support for `portable-atomic`
crate in addition to the support for core atomic types. This support requires atomic CAS support
enabled in the portable atomic crate.
# Examples # Examples
@@ -51,16 +54,14 @@ usage examples.
# Coverage # Coverage
Coverage was generated using [`grcov`](https://github.com/mozilla/grcov). If you have not done so Coverage can be generated using [`llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). If you have not done so
already, install the `llvm-tools-preview`: already, install the tool:
```sh ```sh
rustup component add llvm-tools-preview cargo +stable install cargo-llvm-cov --locked
cargo install grcov --locked
``` ```
After that, you can simply run `coverage.py` to test the project with coverage. You can optionally After this, you can run `cargo llvm-cov nextest` to run all the tests and display coverage.
supply the `--open` flag to open the coverage report in your webbrowser.
# Miri # Miri
-54
View File
@@ -1,54 +0,0 @@
#!/usr/bin/env python3
import os
import logging
import argparse
import webbrowser
_LOGGER = logging.getLogger()
def generate_cov_report(open_report: bool, format: str):
logging.basicConfig(level=logging.INFO)
os.environ["RUSTFLAGS"] = "-Cinstrument-coverage"
os.environ["LLVM_PROFILE_FILE"] = "target/coverage/%p-%m.profraw"
_LOGGER.info("Executing tests with coverage")
os.system("cargo test --all-features")
out_path = "./target/debug/coverage"
if format == "lcov":
out_path = "./target/debug/lcov.info"
os.system(
f"grcov . -s . --binary-path ./target/debug/ -t {format} --branch --ignore-not-existing "
f"-o {out_path}"
)
if format == "lcov":
os.system(
"genhtml -o ./target/debug/coverage/ --show-details --highlight --ignore-errors source "
"--legend ./target/debug/lcov.info"
)
if open_report:
coverage_report_path = os.path.abspath("./target/debug/coverage/index.html")
webbrowser.open_new_tab(coverage_report_path)
_LOGGER.info("Done")
def main():
parser = argparse.ArgumentParser(
description="Generate coverage report and optionally open it in a browser"
)
parser.add_argument(
"--open", action="store_true", help="Open the coverage report in a browser"
)
parser.add_argument(
"--format",
choices=["html", "lcov"],
default="html",
help="Choose report format (html or lcov)",
)
args = parser.parse_args()
generate_cov_report(args.open, args.format)
if __name__ == "__main__":
main()
+36
View File
@@ -0,0 +1,36 @@
all: check build clippy embedded test check-fmt docs coverage
clippy:
cargo clippy -- -D warnings
fmt:
cargo fmt --all
check-fmt:
cargo fmt --all -- --check
check:
cargo check --all-features
embedded:
cargo build --target thumbv7em-none-eabihf --no-default-features
cargo build --target thumbv6m-none-eabi --no-default-features
test:
cargo nextest r --all-features
cargo test --doc
build:
cargo build --all-features
docs:
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --all-features --no-deps
docs-html:
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --all-features --open
coverage:
cargo llvm-cov nextest
coverage-html:
cargo llvm-cov nextest --html --open
+12 -6
View File
@@ -6,8 +6,9 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::string::String; use std::string::String;
use super::TlvLvDataTooLarge; use super::TlvLvDataTooLargeError;
/// Minmum length of a CFDP length-value structure in bytes.
pub const MIN_LV_LEN: usize = 1; pub const MIN_LV_LEN: usize = 1;
/// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8. /// Generic CFDP length-value (LV) abstraction as specified in CFDP 5.1.8.
@@ -63,10 +64,14 @@ pub(crate) fn generic_len_check_deserialization(
} }
impl<'data> Lv<'data> { impl<'data> Lv<'data> {
/// Minimum length of a LV structure in bytes.
pub const MIN_LEN: usize = MIN_LV_LEN;
/// Generic constructor.
#[inline] #[inline]
pub fn new(data: &[u8]) -> Result<Lv, TlvLvDataTooLarge> { pub fn new(data: &[u8]) -> Result<Lv<'_>, TlvLvDataTooLargeError> {
if data.len() > u8::MAX as usize { if data.len() > u8::MAX as usize {
return Err(TlvLvDataTooLarge(data.len())); return Err(TlvLvDataTooLargeError(data.len()));
} }
Ok(Lv { Ok(Lv {
data, data,
@@ -86,7 +91,7 @@ impl<'data> Lv<'data> {
/// Helper function to build a string LV. This is especially useful for the file or directory /// Helper function to build a string LV. This is especially useful for the file or directory
/// path LVs /// path LVs
#[inline] #[inline]
pub fn new_from_str(str_slice: &str) -> Result<Lv, TlvLvDataTooLarge> { pub fn new_from_str(str_slice: &str) -> Result<Lv<'_>, TlvLvDataTooLargeError> {
Self::new(str_slice.as_bytes()) Self::new(str_slice.as_bytes())
} }
@@ -94,7 +99,7 @@ impl<'data> Lv<'data> {
/// path LVs /// path LVs
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[inline] #[inline]
pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvDataTooLarge> { pub fn new_from_string(string: &'data String) -> Result<Lv<'data>, TlvLvDataTooLargeError> {
Self::new(string.as_bytes()) Self::new(string.as_bytes())
} }
@@ -116,6 +121,7 @@ impl<'data> Lv<'data> {
self.data.len() == 0 self.data.len() == 0
} }
/// Raw value part of the LV.
#[inline] #[inline]
pub fn value(&self) -> &[u8] { pub fn value(&self) -> &[u8] {
self.data self.data
@@ -177,7 +183,7 @@ impl<'data> Lv<'data> {
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { mod tests {
use alloc::string::ToString; use alloc::string::ToString;
use super::*; use super::*;
+88 -96
View File
@@ -1,11 +1,8 @@
//! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf). //! Low-level CCSDS File Delivery Protocol (CFDP) support according to [CCSDS 727.0-B-5](https://public.ccsds.org/Pubs/727x0b5.pdf).
use crate::ByteConversionError; use crate::ByteConversionError;
use core::fmt::{Display, Formatter};
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub mod lv; pub mod lv;
pub mod pdu; pub mod pdu;
@@ -16,39 +13,55 @@ pub const CFDP_VERSION_2_NAME: &str = "CCSDS 727.0-B-5";
/// Currently, only this version is supported. /// Currently, only this version is supported.
pub const CFDP_VERSION_2: u8 = 0b001; pub const CFDP_VERSION_2: u8 = 0b001;
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// PDU type.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum PduType { pub enum PduType {
/// File directive PDU.
FileDirective = 0, FileDirective = 0,
/// File data PDU.
FileData = 1, FileData = 1,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// PDU direction.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum Direction { pub enum Direction {
/// Going towards the file receiver.
TowardsReceiver = 0, TowardsReceiver = 0,
/// Going towards the file sender.
TowardsSender = 1, TowardsSender = 1,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// PDU transmission mode.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum TransmissionMode { pub enum TransmissionMode {
/// Acknowledged (class 1) transfer.
Acknowledged = 0, Acknowledged = 0,
/// Unacknowledged (class 2) transfer.
Unacknowledged = 1, Unacknowledged = 1,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// CRC flag.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum CrcFlag { pub enum CrcFlag {
/// No CRC for the packet.
NoCrc = 0, NoCrc = 0,
/// Packet has CRC.
WithCrc = 1, WithCrc = 1,
} }
@@ -71,52 +84,76 @@ impl From<CrcFlag> for bool {
} }
/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) /// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75)
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum SegmentMetadataFlag { pub enum SegmentMetadataFlag {
/// Segment metadata not present.
NotPresent = 0, NotPresent = 0,
/// Segment metadata present.
Present = 1, Present = 1,
} }
/// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75) /// Always 0 and ignored for File Directive PDUs (CCSDS 727.0-B-5 P.75)
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum SegmentationControl { pub enum SegmentationControl {
/// No record boundary preservation.
NoRecordBoundaryPreservation = 0, NoRecordBoundaryPreservation = 0,
/// With record boundary preservation.
WithRecordBoundaryPreservation = 1, WithRecordBoundaryPreservation = 1,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// Fault handler codes according to the CFDP standard.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u3, exhaustive = false)]
#[repr(u8)] #[repr(u8)]
pub enum FaultHandlerCode { pub enum FaultHandlerCode {
/// Notice of cancellation fault handler code.
NoticeOfCancellation = 0b0001, NoticeOfCancellation = 0b0001,
/// Notice of suspension fault handler code.
NoticeOfSuspension = 0b0010, NoticeOfSuspension = 0b0010,
/// Ignore error fault handler code.
IgnoreError = 0b0011, IgnoreError = 0b0011,
/// Abandon transaction fault handler code.
AbandonTransaction = 0b0100, AbandonTransaction = 0b0100,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// CFDP condition codes.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u4, exhaustive = false)]
#[repr(u8)] #[repr(u8)]
pub enum ConditionCode { pub enum ConditionCode {
/// This is not an error condition for which a faulty handler override can be specified /// This is not an error condition for which a faulty handler override can be specified
NoError = 0b0000, NoError = 0b0000,
/// Positive acknowledgement limit reached.
PositiveAckLimitReached = 0b0001, PositiveAckLimitReached = 0b0001,
/// Keep-alive limit reached.
KeepAliveLimitReached = 0b0010, KeepAliveLimitReached = 0b0010,
/// Invalid transmission mode.
InvalidTransmissionMode = 0b0011, InvalidTransmissionMode = 0b0011,
/// Filestore rejection.
FilestoreRejection = 0b0100, FilestoreRejection = 0b0100,
/// File checksum error.
FileChecksumFailure = 0b0101, FileChecksumFailure = 0b0101,
/// File size error.
FileSizeError = 0b0110, FileSizeError = 0b0110,
/// NAK limit reached.
NakLimitReached = 0b0111, NakLimitReached = 0b0111,
/// Inactivity detected.
InactivityDetected = 0b1000, InactivityDetected = 0b1000,
/// Check limit reached.
CheckLimitReached = 0b1010, CheckLimitReached = 0b1010,
/// Unsupported checksum type.
UnsupportedChecksumType = 0b1011, UnsupportedChecksumType = 0b1011,
/// Not an actual fault condition for which fault handler overrides can be specified /// Not an actual fault condition for which fault handler overrides can be specified
SuspendRequestReceived = 0b1110, SuspendRequestReceived = 0b1110,
@@ -124,9 +161,11 @@ pub enum ConditionCode {
CancelRequestReceived = 0b1111, CancelRequestReceived = 0b1111,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// Large file flag.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum LargeFileFlag { pub enum LargeFileFlag {
/// 32 bit maximum file size and FSS size /// 32 bit maximum file size and FSS size
@@ -136,14 +175,16 @@ pub enum LargeFileFlag {
} }
/// Transaction status for the ACK PDU field according to chapter 5.2.4 of the CFDP standard. /// Transaction status for the ACK PDU field according to chapter 5.2.4 of the CFDP standard.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u2, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum TransactionStatus { pub enum TransactionStatus {
/// Transaction is not currently active and the CFDP implementation does not retain a /// Transaction is not currently active and the CFDP implementation does not retain a
/// transaction history. /// transaction history.
Undefined = 0b00, Undefined = 0b00,
/// Transaction is currently active.
Active = 0b01, Active = 0b01,
/// Transaction was active in the past and was terminated. /// Transaction was active in the past and was terminated.
Terminated = 0b10, Terminated = 0b10,
@@ -154,119 +195,70 @@ pub enum TransactionStatus {
/// Checksum types according to the /// Checksum types according to the
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/) /// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/)
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum ChecksumType { pub enum ChecksumType {
/// Modular legacy checksum /// Modular legacy checksum
Modular = 0, Modular = 0,
/// CRC32 Proximity-1.
Crc32Proximity1 = 1, Crc32Proximity1 = 1,
/// CRC32C.
Crc32C = 2, Crc32C = 2,
/// Polynomial: 0x4C11DB7. Preferred checksum for now. /// CRC32. Polynomial: 0x4C11DB7. Preferred checksum for now.
Crc32 = 3, Crc32 = 3,
/// Null checksum (no checksum).
#[default]
NullChecksum = 15, NullChecksum = 15,
} }
impl Default for ChecksumType { /// Raw null checksum.
fn default() -> Self {
Self::NullChecksum
}
}
pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4]; pub const NULL_CHECKSUM_U32: [u8; 4] = [0; 4];
#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// TLV or LV data larger than allowed [u8::MAX].
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TlvLvDataTooLarge(pub usize); #[error("data with size {0} larger than allowed {max} bytes", max = u8::MAX)]
pub struct TlvLvDataTooLargeError(pub usize);
impl Display for TlvLvDataTooLarge { /// First value: Found value. Second value: Expected value if there is one.
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
write!( #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
f, #[cfg_attr(feature = "defmt", derive(defmt::Format))]
"data with size {} larger than allowed {} bytes", #[error("invalid TLV type field, found {found}, expected {expected:?}")]
self.0, pub struct InvalidTlvTypeFieldError {
u8::MAX found: u8,
) expected: Option<u8>,
}
} }
#[cfg(feature = "std")] /// Generic TLV/LV error.
impl Error for TlvLvDataTooLarge {} #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvLvError { pub enum TlvLvError {
DataTooLarge(TlvLvDataTooLarge), /// Data too large error.
ByteConversion(ByteConversionError), #[error("{0}")]
/// First value: Found value. Second value: Expected value if there is one. DataTooLarge(#[from] TlvLvDataTooLargeError),
InvalidTlvTypeField { /// Byte conversion error.
found: u8, #[error("byte conversion error: {0}")]
expected: Option<u8>, ByteConversion(#[from] ByteConversionError),
}, /// Invalid TLV type field error.
/// Logically invalid value length detected. The value length may not exceed 255 bytes. #[error("{0}")]
/// Depending on the concrete TLV type, the value length may also be logically invalid. InvalidTlvTypeField(#[from] InvalidTlvTypeFieldError),
/// Invalid value length.
#[error("invalid value length {0}")]
InvalidValueLength(usize), InvalidValueLength(usize),
/// Only applies to filestore requests and responses. Second name was missing where one is /// Only applies to filestore requests and responses. Second name was missing where one is
/// expected. /// expected.
#[error("second name missing for filestore request or response")]
SecondNameMissing, SecondNameMissing,
/// Invalid action code for filestore requests or responses. /// Invalid action code for filestore requests or responses.
#[error("invalid action code {0}")]
InvalidFilestoreActionCode(u8), InvalidFilestoreActionCode(u8),
} }
impl From<TlvLvDataTooLarge> for TlvLvError {
fn from(value: TlvLvDataTooLarge) -> Self {
Self::DataTooLarge(value)
}
}
impl From<ByteConversionError> for TlvLvError {
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl Display for TlvLvError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
TlvLvError::DataTooLarge(e) => {
write!(f, "{}", e)
}
TlvLvError::ByteConversion(e) => {
write!(f, "tlv or lv byte conversion: {}", e)
}
TlvLvError::InvalidTlvTypeField { found, expected } => {
write!(
f,
"invalid TLV type field, found {found}, expected {expected:?}"
)
}
TlvLvError::InvalidValueLength(len) => {
write!(f, "invalid value length {len}")
}
TlvLvError::SecondNameMissing => {
write!(f, "second name missing for filestore request or response")
}
TlvLvError::InvalidFilestoreActionCode(raw) => {
write!(f, "invalid filestore action code with raw value {raw}")
}
}
}
}
#[cfg(feature = "std")]
impl Error for TlvLvError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TlvLvError::DataTooLarge(e) => Some(e),
TlvLvError::ByteConversion(e) => Some(e),
_ => None,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+115 -46
View File
@@ -1,3 +1,4 @@
//! # Acknowledgement (ACK) PDU packet implementation.
use crate::{ use crate::{
cfdp::{ConditionCode, CrcFlag, Direction, TransactionStatus}, cfdp::{ConditionCode, CrcFlag, Direction, TransactionStatus},
ByteConversionError, ByteConversionError,
@@ -10,6 +11,11 @@ use super::{
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Invalid [FileDirectiveType] of the acknowledged PDU error.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("invalid directive code of acknowledged PDU")]
pub struct InvalidAckedDirectiveCodeError(pub FileDirectiveType);
/// ACK PDU abstraction. /// ACK PDU abstraction.
/// ///
/// For more information, refer to CFDP chapter 5.2.4. /// For more information, refer to CFDP chapter 5.2.4.
@@ -24,21 +30,19 @@ pub struct AckPdu {
} }
impl AckPdu { impl AckPdu {
/// Constructor.
pub fn new( pub fn new(
mut pdu_header: PduHeader, mut pdu_header: PduHeader,
directive_code_of_acked_pdu: FileDirectiveType, directive_code_of_acked_pdu: FileDirectiveType,
condition_code: ConditionCode, condition_code: ConditionCode,
transaction_status: TransactionStatus, transaction_status: TransactionStatus,
) -> Result<Self, PduError> { ) -> Result<Self, InvalidAckedDirectiveCodeError> {
if directive_code_of_acked_pdu == FileDirectiveType::EofPdu { if directive_code_of_acked_pdu == FileDirectiveType::Eof {
pdu_header.pdu_conf.direction = Direction::TowardsSender; pdu_header.pdu_conf.direction = Direction::TowardsSender;
} else if directive_code_of_acked_pdu == FileDirectiveType::FinishedPdu { } else if directive_code_of_acked_pdu == FileDirectiveType::Finished {
pdu_header.pdu_conf.direction = Direction::TowardsReceiver; pdu_header.pdu_conf.direction = Direction::TowardsReceiver;
} else { } else {
return Err(PduError::InvalidDirectiveType { return Err(InvalidAckedDirectiveCodeError(directive_code_of_acked_pdu));
found: directive_code_of_acked_pdu as u8,
expected: None,
});
} }
// Force correct direction flag. // Force correct direction flag.
let mut ack_pdu = Self { let mut ack_pdu = Self {
@@ -51,6 +55,9 @@ impl AckPdu {
Ok(ack_pdu) Ok(ack_pdu)
} }
/// Constructor for an ACK PDU acknowledging an EOF PDU.
///
/// Relevant for the file receiver.
pub fn new_for_eof_pdu( pub fn new_for_eof_pdu(
pdu_header: PduHeader, pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,
@@ -59,13 +66,16 @@ impl AckPdu {
// Unwrap okay here, [new] can only fail on invalid directive codes. // Unwrap okay here, [new] can only fail on invalid directive codes.
Self::new( Self::new(
pdu_header, pdu_header,
FileDirectiveType::EofPdu, FileDirectiveType::Eof,
condition_code, condition_code,
transaction_status, transaction_status,
) )
.unwrap() .unwrap()
} }
/// Constructor for an ACK PDU acknowledging a Finished PDU.
///
/// Relevant for the file sender.
pub fn new_for_finished_pdu( pub fn new_for_finished_pdu(
pdu_header: PduHeader, pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,
@@ -74,29 +84,38 @@ impl AckPdu {
// Unwrap okay here, [new] can only fail on invalid directive codes. // Unwrap okay here, [new] can only fail on invalid directive codes.
Self::new( Self::new(
pdu_header, pdu_header,
FileDirectiveType::FinishedPdu, FileDirectiveType::Finished,
condition_code, condition_code,
transaction_status, transaction_status,
) )
.unwrap() .unwrap()
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader { pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header &self.pdu_header
} }
/// Directive code of the acknowledged PDU.
#[inline]
pub fn directive_code_of_acked_pdu(&self) -> FileDirectiveType { pub fn directive_code_of_acked_pdu(&self) -> FileDirectiveType {
self.directive_code_of_acked_pdu self.directive_code_of_acked_pdu
} }
/// Condition code.
#[inline]
pub fn condition_code(&self) -> ConditionCode { pub fn condition_code(&self) -> ConditionCode {
self.condition_code self.condition_code
} }
/// Transaction status.
#[inline]
pub fn transaction_status(&self) -> TransactionStatus { pub fn transaction_status(&self) -> TransactionStatus {
self.transaction_status self.transaction_status
} }
#[inline]
fn calc_pdu_datafield_len(&self) -> usize { fn calc_pdu_datafield_len(&self) -> usize {
if self.crc_flag() == CrcFlag::WithCrc { if self.crc_flag() == CrcFlag::WithCrc {
return 5; return 5;
@@ -104,6 +123,7 @@ impl AckPdu {
3 3
} }
/// Construct [Self] from the provided byte slice.
pub fn from_bytes(buf: &[u8]) -> Result<AckPdu, PduError> { pub fn from_bytes(buf: &[u8]) -> Result<AckPdu, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
@@ -111,13 +131,13 @@ impl AckPdu {
let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
PduError::InvalidDirectiveType { PduError::InvalidDirectiveType {
found: buf[current_idx], found: buf[current_idx],
expected: Some(FileDirectiveType::AckPdu), expected: Some(FileDirectiveType::Ack),
} }
})?; })?;
if directive_type != FileDirectiveType::AckPdu { if directive_type != FileDirectiveType::Ack {
return Err(PduError::WrongDirectiveType { return Err(PduError::WrongDirectiveType {
found: directive_type, found: directive_type,
expected: FileDirectiveType::AckPdu, expected: FileDirectiveType::Ack,
}); });
} }
current_idx += 1; current_idx += 1;
@@ -128,8 +148,8 @@ impl AckPdu {
expected: None, expected: None,
} }
})?; })?;
if acked_directive_type != FileDirectiveType::EofPdu if acked_directive_type != FileDirectiveType::Eof
&& acked_directive_type != FileDirectiveType::FinishedPdu && acked_directive_type != FileDirectiveType::Finished
{ {
return Err(PduError::InvalidDirectiveType { return Err(PduError::InvalidDirectiveType {
found: acked_directive_type as u8, found: acked_directive_type as u8,
@@ -140,27 +160,18 @@ impl AckPdu {
let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111) let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111)
.map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?; .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?;
let transaction_status = TransactionStatus::try_from(buf[current_idx] & 0b11).unwrap(); let transaction_status = TransactionStatus::try_from(buf[current_idx] & 0b11).unwrap();
Self::new( // Unwrap okay, validity of acked directive code was checked.
Ok(Self::new(
pdu_header, pdu_header,
acked_directive_type, acked_directive_type,
condition_code, condition_code,
transaction_status, transaction_status,
) )
} .unwrap())
}
impl CfdpPdu for AckPdu {
fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
} }
fn file_directive_type(&self) -> Option<FileDirectiveType> { /// Write [Self] to the provided buffer and returns the written size.
Some(FileDirectiveType::AckPdu) pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
}
}
impl WritablePduPacket for AckPdu {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let expected_len = self.len_written(); let expected_len = self.len_written();
if buf.len() < expected_len { if buf.len() < expected_len {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
@@ -170,11 +181,11 @@ impl WritablePduPacket for AckPdu {
.into()); .into());
} }
let mut current_idx = self.pdu_header.write_to_bytes(buf)?; let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
buf[current_idx] = FileDirectiveType::AckPdu as u8; buf[current_idx] = FileDirectiveType::Ack as u8;
current_idx += 1; current_idx += 1;
buf[current_idx] = (self.directive_code_of_acked_pdu as u8) << 4; buf[current_idx] = (self.directive_code_of_acked_pdu as u8) << 4;
if self.directive_code_of_acked_pdu == FileDirectiveType::FinishedPdu { if self.directive_code_of_acked_pdu == FileDirectiveType::Finished {
// This is the directive subtype code. It needs to be set to 0b0001 if the ACK PDU // This is the directive subtype code. It needs to be set to 0b0001 if the ACK PDU
// acknowledges a Finished PDU, and to 0b0000 otherwise. // acknowledges a Finished PDU, and to 0b0000 otherwise.
buf[current_idx] |= 0b0001; buf[current_idx] |= 0b0001;
@@ -188,11 +199,34 @@ impl WritablePduPacket for AckPdu {
Ok(current_idx) Ok(current_idx)
} }
fn len_written(&self) -> usize { /// Length of the written PDU in bytes.
pub fn len_written(&self) -> usize {
self.pdu_header.header_len() + self.calc_pdu_datafield_len() self.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
} }
impl CfdpPdu for AckPdu {
#[inline]
fn pdu_header(&self) -> &PduHeader {
self.pdu_header()
}
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::Ack)
}
}
impl WritablePduPacket for AckPdu {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
self.write_to_bytes(buf)
}
fn len_written(&self) -> usize {
self.len_written()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cfdp::{ use crate::cfdp::{
@@ -211,10 +245,7 @@ mod tests {
assert_eq!(ack_pdu.crc_flag(), expected_crc_flag); assert_eq!(ack_pdu.crc_flag(), expected_crc_flag);
assert_eq!(ack_pdu.file_flag(), LargeFileFlag::Normal); assert_eq!(ack_pdu.file_flag(), LargeFileFlag::Normal);
assert_eq!(ack_pdu.pdu_type(), PduType::FileDirective); assert_eq!(ack_pdu.pdu_type(), PduType::FileDirective);
assert_eq!( assert_eq!(ack_pdu.file_directive_type(), Some(FileDirectiveType::Ack));
ack_pdu.file_directive_type(),
Some(FileDirectiveType::AckPdu)
);
assert_eq!(ack_pdu.transmission_mode(), TransmissionMode::Acknowledged); assert_eq!(ack_pdu.transmission_mode(), TransmissionMode::Acknowledged);
assert_eq!(ack_pdu.direction(), expected_dir); assert_eq!(ack_pdu.direction(), expected_dir);
assert_eq!(ack_pdu.source_id(), TEST_SRC_ID.into()); assert_eq!(ack_pdu.source_id(), TEST_SRC_ID.into());
@@ -225,17 +256,17 @@ mod tests {
#[test] #[test]
fn test_basic() { fn test_basic() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new( let ack_pdu = AckPdu::new(
pdu_header, pdu_header,
FileDirectiveType::FinishedPdu, FileDirectiveType::Finished,
ConditionCode::NoError, ConditionCode::NoError,
TransactionStatus::Active, TransactionStatus::Active,
) )
.expect("creating ACK PDU failed"); .expect("creating ACK PDU failed");
assert_eq!( assert_eq!(
ack_pdu.directive_code_of_acked_pdu(), ack_pdu.directive_code_of_acked_pdu(),
FileDirectiveType::FinishedPdu FileDirectiveType::Finished
); );
verify_state(&ack_pdu, CrcFlag::NoCrc, Direction::TowardsReceiver); verify_state(&ack_pdu, CrcFlag::NoCrc, Direction::TowardsReceiver);
} }
@@ -245,7 +276,7 @@ mod tests {
transaction_status: TransactionStatus, transaction_status: TransactionStatus,
) { ) {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new_for_finished_pdu(pdu_header, condition_code, transaction_status); let ack_pdu = AckPdu::new_for_finished_pdu(pdu_header, condition_code, transaction_status);
let mut buf: [u8; 64] = [0; 64]; let mut buf: [u8; 64] = [0; 64];
let res = ack_pdu.write_to_bytes(&mut buf); let res = ack_pdu.write_to_bytes(&mut buf);
@@ -254,8 +285,8 @@ mod tests {
assert_eq!(written, ack_pdu.len_written()); assert_eq!(written, ack_pdu.len_written());
verify_raw_header(ack_pdu.pdu_header(), &buf); verify_raw_header(ack_pdu.pdu_header(), &buf);
assert_eq!(buf[7], FileDirectiveType::AckPdu as u8); assert_eq!(buf[7], FileDirectiveType::Ack as u8);
assert_eq!((buf[8] >> 4) & 0b1111, FileDirectiveType::FinishedPdu as u8); assert_eq!((buf[8] >> 4) & 0b1111, FileDirectiveType::Finished as u8);
assert_eq!(buf[8] & 0b1111, 0b0001); assert_eq!(buf[8] & 0b1111, 0b0001);
assert_eq!(buf[9] >> 4 & 0b1111, condition_code as u8); assert_eq!(buf[9] >> 4 & 0b1111, condition_code as u8);
assert_eq!(buf[9] & 0b11, transaction_status as u8); assert_eq!(buf[9] & 0b11, transaction_status as u8);
@@ -267,15 +298,53 @@ mod tests {
generic_serialization_test(ConditionCode::NoError, TransactionStatus::Active); generic_serialization_test(ConditionCode::NoError, TransactionStatus::Active);
} }
#[test]
fn test_serialization_too_small() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new(
pdu_header,
FileDirectiveType::Finished,
ConditionCode::NoError,
TransactionStatus::Active,
)
.expect("creating ACK PDU failed");
if let Err(PduError::ByteConversion(ByteConversionError::ToSliceTooSmall {
found,
expected,
})) = ack_pdu.write_to_bytes(&mut [0; 5])
{
assert_eq!(found, 5);
assert_eq!(expected, ack_pdu.len_written());
} else {
panic!("serialization should have failed");
}
}
#[test] #[test]
fn test_serialization_fs_error() { fn test_serialization_fs_error() {
generic_serialization_test(ConditionCode::FileSizeError, TransactionStatus::Terminated); generic_serialization_test(ConditionCode::FileSizeError, TransactionStatus::Terminated);
} }
#[test]
fn test_invalid_directive_code_of_acked_pdu() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
assert_eq!(
AckPdu::new(
pdu_header,
FileDirectiveType::Metadata,
ConditionCode::NoError,
TransactionStatus::Active,
)
.unwrap_err(),
InvalidAckedDirectiveCodeError(FileDirectiveType::Metadata)
);
}
#[test] #[test]
fn test_deserialization() { fn test_deserialization() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new_for_finished_pdu( let ack_pdu = AckPdu::new_for_finished_pdu(
pdu_header, pdu_header,
ConditionCode::NoError, ConditionCode::NoError,
@@ -290,7 +359,7 @@ mod tests {
#[test] #[test]
fn test_with_crc() { fn test_with_crc() {
let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new_for_finished_pdu( let ack_pdu = AckPdu::new_for_finished_pdu(
pdu_header, pdu_header,
ConditionCode::NoError, ConditionCode::NoError,
@@ -307,7 +376,7 @@ mod tests {
#[test] #[test]
fn test_for_eof_pdu() { fn test_for_eof_pdu() {
let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new_for_eof_pdu( let ack_pdu = AckPdu::new_for_eof_pdu(
pdu_header, pdu_header,
ConditionCode::NoError, ConditionCode::NoError,
@@ -315,7 +384,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
ack_pdu.directive_code_of_acked_pdu(), ack_pdu.directive_code_of_acked_pdu(),
FileDirectiveType::EofPdu FileDirectiveType::Eof
); );
verify_state(&ack_pdu, CrcFlag::WithCrc, Direction::TowardsSender); verify_state(&ack_pdu, CrcFlag::WithCrc, Direction::TowardsSender);
} }
@@ -324,7 +393,7 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_ack_pdu_serialization() { fn test_ack_pdu_serialization() {
let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let ack_pdu = AckPdu::new_for_eof_pdu( let ack_pdu = AckPdu::new_for_eof_pdu(
pdu_header, pdu_header,
ConditionCode::NoError, ConditionCode::NoError,
+53 -32
View File
@@ -1,3 +1,4 @@
//! # End-of-File (EOF) PDU packet implementation.
use crate::cfdp::pdu::{ use crate::cfdp::pdu::{
add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field,
FileDirectiveType, PduError, PduHeader, FileDirectiveType, PduError, PduHeader,
@@ -25,6 +26,7 @@ pub struct EofPdu {
} }
impl EofPdu { impl EofPdu {
/// Constructor.
pub fn new( pub fn new(
mut pdu_header: PduHeader, mut pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,
@@ -45,6 +47,7 @@ impl EofPdu {
eof_pdu eof_pdu
} }
/// Constructor for no error EOF PDUs.
pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self { pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self {
Self::new( Self::new(
pdu_header, pdu_header,
@@ -55,18 +58,26 @@ impl EofPdu {
) )
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader { pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header &self.pdu_header
} }
/// Condition code.
#[inline]
pub fn condition_code(&self) -> ConditionCode { pub fn condition_code(&self) -> ConditionCode {
self.condition_code self.condition_code
} }
/// File checksum.
#[inline]
pub fn file_checksum(&self) -> u32 { pub fn file_checksum(&self) -> u32 {
self.file_checksum self.file_checksum
} }
/// File size.
#[inline]
pub fn file_size(&self) -> u64 { pub fn file_size(&self) -> u64 {
self.file_size self.file_size
} }
@@ -86,6 +97,7 @@ impl EofPdu {
len len
} }
/// Construct [Self] from the provided byte slice.
pub fn from_bytes(buf: &[u8]) -> Result<EofPdu, PduError> { pub fn from_bytes(buf: &[u8]) -> Result<EofPdu, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
@@ -98,13 +110,13 @@ impl EofPdu {
let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
PduError::InvalidDirectiveType { PduError::InvalidDirectiveType {
found: buf[current_idx], found: buf[current_idx],
expected: Some(FileDirectiveType::EofPdu), expected: Some(FileDirectiveType::Eof),
} }
})?; })?;
if directive_type != FileDirectiveType::EofPdu { if directive_type != FileDirectiveType::Eof {
return Err(PduError::WrongDirectiveType { return Err(PduError::WrongDirectiveType {
found: directive_type, found: directive_type,
expected: FileDirectiveType::EofPdu, expected: FileDirectiveType::Eof,
}); });
} }
current_idx += 1; current_idx += 1;
@@ -129,20 +141,9 @@ impl EofPdu {
fault_location, fault_location,
}) })
} }
}
impl CfdpPdu for EofPdu { /// Write [Self] to the provided buffer and returns the written size.
fn pdu_header(&self) -> &PduHeader { pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
&self.pdu_header
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::EofPdu)
}
}
impl WritablePduPacket for EofPdu {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let expected_len = self.len_written(); let expected_len = self.len_written();
if buf.len() < expected_len { if buf.len() < expected_len {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
@@ -152,7 +153,7 @@ impl WritablePduPacket for EofPdu {
.into()); .into());
} }
let mut current_idx = self.pdu_header.write_to_bytes(buf)?; let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
buf[current_idx] = FileDirectiveType::EofPdu as u8; buf[current_idx] = FileDirectiveType::Eof as u8;
current_idx += 1; current_idx += 1;
buf[current_idx] = (self.condition_code as u8) << 4; buf[current_idx] = (self.condition_code as u8) << 4;
current_idx += 1; current_idx += 1;
@@ -172,11 +173,34 @@ impl WritablePduPacket for EofPdu {
Ok(current_idx) Ok(current_idx)
} }
fn len_written(&self) -> usize { /// Length of the written PDU in bytes.
pub fn len_written(&self) -> usize {
self.pdu_header.header_len() + self.calc_pdu_datafield_len() self.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
} }
impl CfdpPdu for EofPdu {
#[inline]
fn pdu_header(&self) -> &PduHeader {
self.pdu_header()
}
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::Eof)
}
}
impl WritablePduPacket for EofPdu {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
self.write_to_bytes(buf)
}
fn len_written(&self) -> usize {
self.len_written()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -206,10 +230,7 @@ mod tests {
assert_eq!(eof_pdu.crc_flag(), crc_flag); assert_eq!(eof_pdu.crc_flag(), crc_flag);
assert_eq!(eof_pdu.file_flag(), file_flag); assert_eq!(eof_pdu.file_flag(), file_flag);
assert_eq!(eof_pdu.pdu_type(), PduType::FileDirective); assert_eq!(eof_pdu.pdu_type(), PduType::FileDirective);
assert_eq!( assert_eq!(eof_pdu.file_directive_type(), Some(FileDirectiveType::Eof));
eof_pdu.file_directive_type(),
Some(FileDirectiveType::EofPdu)
);
assert_eq!(eof_pdu.transmission_mode(), TransmissionMode::Acknowledged); assert_eq!(eof_pdu.transmission_mode(), TransmissionMode::Acknowledged);
assert_eq!(eof_pdu.direction(), Direction::TowardsReceiver); assert_eq!(eof_pdu.direction(), Direction::TowardsReceiver);
assert_eq!(eof_pdu.source_id(), TEST_SRC_ID.into()); assert_eq!(eof_pdu.source_id(), TEST_SRC_ID.into());
@@ -220,7 +241,7 @@ mod tests {
#[test] #[test]
fn test_basic() { fn test_basic() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 4 + 4); assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 4 + 4);
verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Normal); verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Normal);
@@ -229,7 +250,7 @@ mod tests {
#[test] #[test]
fn test_serialization() { fn test_serialization() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
let mut buf: [u8; 64] = [0; 64]; let mut buf: [u8; 64] = [0; 64];
let res = eof_pdu.write_to_bytes(&mut buf); let res = eof_pdu.write_to_bytes(&mut buf);
@@ -238,7 +259,7 @@ mod tests {
assert_eq!(written, eof_pdu.len_written()); assert_eq!(written, eof_pdu.len_written());
verify_raw_header(eof_pdu.pdu_header(), &buf); verify_raw_header(eof_pdu.pdu_header(), &buf);
let mut current_idx = eof_pdu.pdu_header().header_len(); let mut current_idx = eof_pdu.pdu_header().header_len();
buf[current_idx] = FileDirectiveType::EofPdu as u8; buf[current_idx] = FileDirectiveType::Eof as u8;
current_idx += 1; current_idx += 1;
assert_eq!( assert_eq!(
(buf[current_idx] >> 4) & 0b1111, (buf[current_idx] >> 4) & 0b1111,
@@ -261,7 +282,7 @@ mod tests {
#[test] #[test]
fn test_deserialization() { fn test_deserialization() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
let mut buf: [u8; 64] = [0; 64]; let mut buf: [u8; 64] = [0; 64];
eof_pdu.write_to_bytes(&mut buf).unwrap(); eof_pdu.write_to_bytes(&mut buf).unwrap();
@@ -276,7 +297,7 @@ mod tests {
#[test] #[test]
fn test_write_to_vec() { fn test_write_to_vec() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
let mut buf: [u8; 64] = [0; 64]; let mut buf: [u8; 64] = [0; 64];
let written = eof_pdu.write_to_bytes(&mut buf).unwrap(); let written = eof_pdu.write_to_bytes(&mut buf).unwrap();
@@ -287,7 +308,7 @@ mod tests {
#[test] #[test]
fn test_with_crc() { fn test_with_crc() {
let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
let mut buf: [u8; 64] = [0; 64]; let mut buf: [u8; 64] = [0; 64];
let written = eof_pdu.write_to_bytes(&mut buf).unwrap(); let written = eof_pdu.write_to_bytes(&mut buf).unwrap();
@@ -297,7 +318,7 @@ mod tests {
buf[written - 1] -= 1; buf[written - 1] -= 1;
let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16; let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16;
let error = EofPdu::from_bytes(&buf).unwrap_err(); let error = EofPdu::from_bytes(&buf).unwrap_err();
if let PduError::ChecksumError(e) = error { if let PduError::Checksum(e) = error {
assert_eq!(e, crc); assert_eq!(e, crc);
} else { } else {
panic!("expected crc error"); panic!("expected crc error");
@@ -307,7 +328,7 @@ mod tests {
#[test] #[test]
fn test_with_large_file_flag() { fn test_with_large_file_flag() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Large); verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Large);
assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 8 + 4); assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 8 + 4);
@@ -317,14 +338,14 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_eof_serde() { fn test_eof_serde() {
let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12); let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
generic_serde_test(eof_pdu); generic_serde_test(eof_pdu);
} }
fn generic_test_with_fault_location_and_error(crc: CrcFlag) { fn generic_test_with_fault_location_and_error(crc: CrcFlag) {
let pdu_conf = common_pdu_conf(crc, LargeFileFlag::Normal); let pdu_conf = common_pdu_conf(crc, LargeFileFlag::Normal);
let pdu_header = PduHeader::new_no_file_data(pdu_conf, 0); let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
let eof_pdu = EofPdu::new( let eof_pdu = EofPdu::new(
pdu_header, pdu_header,
ConditionCode::FileChecksumFailure, ConditionCode::FileChecksumFailure,
+92 -40
View File
@@ -1,3 +1,4 @@
//! # File Data PDU packet implementation
use crate::cfdp::pdu::{ use crate::cfdp::pdu::{
add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field, add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field,
PduError, PduHeader, PduError, PduHeader,
@@ -10,16 +11,24 @@ use serde::{Deserialize, Serialize};
use super::{CfdpPdu, FileDirectiveType, WritablePduPacket}; use super::{CfdpPdu, FileDirectiveType, WritablePduPacket};
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] /// Record continuation state for segment metadata.
#[derive(Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u2, exhaustive = true)]
#[repr(u8)] #[repr(u8)]
pub enum RecordContinuationState { pub enum RecordContinuationState {
/// No start and no end.
NoStartNoEnd = 0b00, NoStartNoEnd = 0b00,
/// Start without end.
StartWithoutEnd = 0b01, StartWithoutEnd = 0b01,
/// End without start.
EndWithoutStart = 0b10, EndWithoutStart = 0b10,
/// Start and end.
StartAndEnd = 0b11, StartAndEnd = 0b11,
} }
/// Segment metadata structure.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SegmentMetadata<'seg_meta> { pub struct SegmentMetadata<'seg_meta> {
@@ -28,6 +37,7 @@ pub struct SegmentMetadata<'seg_meta> {
} }
impl<'seg_meta> SegmentMetadata<'seg_meta> { impl<'seg_meta> SegmentMetadata<'seg_meta> {
/// Constructor.
pub fn new( pub fn new(
record_continuation_state: RecordContinuationState, record_continuation_state: RecordContinuationState,
metadata: Option<&'seg_meta [u8]>, metadata: Option<&'seg_meta [u8]>,
@@ -43,24 +53,30 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> {
}) })
} }
/// Record continuation state.
#[inline]
pub fn record_continuation_state(&self) -> RecordContinuationState { pub fn record_continuation_state(&self) -> RecordContinuationState {
self.record_continuation_state self.record_continuation_state
} }
/// Raw metadata slice.
#[inline]
pub fn metadata(&self) -> Option<&'seg_meta [u8]> { pub fn metadata(&self) -> Option<&'seg_meta [u8]> {
self.metadata self.metadata
} }
pub fn written_len(&self) -> usize { /// Length of the written segment metadata structure.
#[inline]
pub fn len_written(&self) -> usize {
// Map empty metadata to 0 and slice to its length. // Map empty metadata to 0 and slice to its length.
1 + self.metadata.map_or(0, |meta| meta.len()) 1 + self.metadata.map_or(0, |meta| meta.len())
} }
pub(crate) fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { pub(crate) fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
if buf.len() < self.written_len() { if buf.len() < self.len_written() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(), found: buf.len(),
expected: self.written_len(), expected: self.len_written(),
}); });
} }
buf[0] = ((self.record_continuation_state as u8) << 6) buf[0] = ((self.record_continuation_state as u8) << 6)
@@ -68,7 +84,7 @@ impl<'seg_meta> SegmentMetadata<'seg_meta> {
if let Some(metadata) = self.metadata { if let Some(metadata) = self.metadata {
buf[1..1 + metadata.len()].copy_from_slice(metadata) buf[1..1 + metadata.len()].copy_from_slice(metadata)
} }
Ok(self.written_len()) Ok(self.len_written())
} }
pub(crate) fn from_bytes(buf: &'seg_meta [u8]) -> Result<Self, ByteConversionError> { pub(crate) fn from_bytes(buf: &'seg_meta [u8]) -> Result<Self, ByteConversionError> {
@@ -102,10 +118,12 @@ struct FdPduBase<'seg_meta> {
} }
impl CfdpPdu for FdPduBase<'_> { impl CfdpPdu for FdPduBase<'_> {
#[inline]
fn pdu_header(&self) -> &PduHeader { fn pdu_header(&self) -> &PduHeader {
&self.pdu_header self.pdu_header()
} }
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> { fn file_directive_type(&self) -> Option<FileDirectiveType> {
None None
} }
@@ -117,8 +135,8 @@ impl FdPduBase<'_> {
if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large { if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large {
len += 4; len += 4;
} }
if self.segment_metadata.is_some() { if let Some(segment_metadata) = self.segment_metadata {
len += self.segment_metadata.as_ref().unwrap().written_len() len += segment_metadata.len_written()
} }
len += file_data_len as usize; len += file_data_len as usize;
if self.crc_flag() == CrcFlag::WithCrc { if self.crc_flag() == CrcFlag::WithCrc {
@@ -129,12 +147,8 @@ impl FdPduBase<'_> {
fn write_common_fields_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> { fn write_common_fields_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let mut current_idx = self.pdu_header.write_to_bytes(buf)?; let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
if self.segment_metadata.is_some() { if let Some(segment_metadata) = self.segment_metadata {
current_idx += self current_idx += segment_metadata.write_to_bytes(&mut buf[current_idx..])?;
.segment_metadata
.as_ref()
.unwrap()
.write_to_bytes(&mut buf[current_idx..])?;
} }
current_idx += write_fss_field( current_idx += write_fss_field(
self.pdu_header.common_pdu_conf().file_flag, self.pdu_header.common_pdu_conf().file_flag,
@@ -143,6 +157,11 @@ impl FdPduBase<'_> {
)?; )?;
Ok(current_idx) Ok(current_idx)
} }
#[inline]
pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
}
} }
/// File Data PDU abstraction. /// File Data PDU abstraction.
@@ -157,24 +176,27 @@ pub struct FileDataPdu<'seg_meta, 'file_data> {
} }
impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> { impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
/// Constructor for a file data PDU including segment metadata.
pub fn new_with_seg_metadata( pub fn new_with_seg_metadata(
pdu_header: PduHeader, pdu_header: PduHeader,
segment_metadata: SegmentMetadata<'seg_meta>, segment_metadata: SegmentMetadata<'seg_meta>,
offset: u64, offset: u64,
file_data: &'file_data [u8], file_data: &'file_data [u8],
) -> Self { ) -> Self {
Self::new_generic(pdu_header, Some(segment_metadata), offset, file_data) Self::new(pdu_header, Some(segment_metadata), offset, file_data)
} }
/// Constructor for a file data PDU without segment metadata.
pub fn new_no_seg_metadata( pub fn new_no_seg_metadata(
pdu_header: PduHeader, pdu_header: PduHeader,
offset: u64, offset: u64,
file_data: &'file_data [u8], file_data: &'file_data [u8],
) -> Self { ) -> Self {
Self::new_generic(pdu_header, None, offset, file_data) Self::new(pdu_header, None, offset, file_data)
} }
pub fn new_generic( /// Generic constructor for a file data PDU.
pub fn new(
mut pdu_header: PduHeader, mut pdu_header: PduHeader,
segment_metadata: Option<SegmentMetadata<'seg_meta>>, segment_metadata: Option<SegmentMetadata<'seg_meta>>,
offset: u64, offset: u64,
@@ -201,18 +223,31 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
.calc_pdu_datafield_len(self.file_data.len() as u64) .calc_pdu_datafield_len(self.file_data.len() as u64)
} }
pub fn segment_metadata(&self) -> Option<&SegmentMetadata> { /// Optional segment metadata.
#[inline]
pub fn segment_metadata(&self) -> Option<&SegmentMetadata<'_>> {
self.common.segment_metadata.as_ref() self.common.segment_metadata.as_ref()
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader {
self.common.pdu_header()
}
/// File data offset.
#[inline]
pub fn offset(&self) -> u64 { pub fn offset(&self) -> u64 {
self.common.offset self.common.offset
} }
/// File data.
#[inline]
pub fn file_data(&self) -> &'file_data [u8] { pub fn file_data(&self) -> &'file_data [u8] {
self.file_data self.file_data
} }
/// Read [Self] from the provided buffer.
pub fn from_bytes<'buf: 'seg_meta + 'file_data>(buf: &'buf [u8]) -> Result<Self, PduError> { pub fn from_bytes<'buf: 'seg_meta + 'file_data>(buf: &'buf [u8]) -> Result<Self, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
@@ -221,7 +256,7 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
let mut segment_metadata = None; let mut segment_metadata = None;
if pdu_header.seg_metadata_flag == SegmentMetadataFlag::Present { if pdu_header.seg_metadata_flag == SegmentMetadataFlag::Present {
segment_metadata = Some(SegmentMetadata::from_bytes(&buf[current_idx..])?); segment_metadata = Some(SegmentMetadata::from_bytes(&buf[current_idx..])?);
current_idx += segment_metadata.as_ref().unwrap().written_len(); current_idx += segment_metadata.as_ref().unwrap().len_written();
} }
let (fss, offset) = read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]); let (fss, offset) = read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]);
current_idx += fss; current_idx += fss;
@@ -241,19 +276,9 @@ impl<'seg_meta, 'file_data> FileDataPdu<'seg_meta, 'file_data> {
file_data: &buf[current_idx..full_len_without_crc], file_data: &buf[current_idx..full_len_without_crc],
}) })
} }
}
impl CfdpPdu for FileDataPdu<'_, '_> {
fn pdu_header(&self) -> &PduHeader {
&self.common.pdu_header
}
fn file_directive_type(&self) -> Option<FileDirectiveType> { /// Write [Self] to the provided buffer and returns the written size.
None pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
}
}
impl WritablePduPacket for FileDataPdu<'_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
if buf.len() < self.len_written() { if buf.len() < self.len_written() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(), found: buf.len(),
@@ -271,10 +296,32 @@ impl WritablePduPacket for FileDataPdu<'_, '_> {
Ok(current_idx) Ok(current_idx)
} }
fn len_written(&self) -> usize { /// Length of the written PDU.
pub fn len_written(&self) -> usize {
self.common.pdu_header.header_len() + self.calc_pdu_datafield_len() self.common.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
} }
impl CfdpPdu for FileDataPdu<'_, '_> {
#[inline]
fn pdu_header(&self) -> &PduHeader {
&self.common.pdu_header
}
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> {
None
}
}
impl WritablePduPacket for FileDataPdu<'_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
self.write_to_bytes(buf)
}
fn len_written(&self) -> usize {
self.len_written()
}
}
/// File Data PDU creator abstraction. /// File Data PDU creator abstraction.
/// ///
@@ -292,20 +339,23 @@ pub struct FileDataPduCreatorWithReservedDatafield<'seg_meta> {
} }
impl<'seg_meta> FileDataPduCreatorWithReservedDatafield<'seg_meta> { impl<'seg_meta> FileDataPduCreatorWithReservedDatafield<'seg_meta> {
/// Constructor for a file data PDU including segment metadata.
pub fn new_with_seg_metadata( pub fn new_with_seg_metadata(
pdu_header: PduHeader, pdu_header: PduHeader,
segment_metadata: SegmentMetadata<'seg_meta>, segment_metadata: SegmentMetadata<'seg_meta>,
offset: u64, offset: u64,
file_data_len: u64, file_data_len: u64,
) -> Self { ) -> Self {
Self::new_generic(pdu_header, Some(segment_metadata), offset, file_data_len) Self::new(pdu_header, Some(segment_metadata), offset, file_data_len)
} }
/// Constructor for a file data PDU without segment metadata.
pub fn new_no_seg_metadata(pdu_header: PduHeader, offset: u64, file_data_len: u64) -> Self { pub fn new_no_seg_metadata(pdu_header: PduHeader, offset: u64, file_data_len: u64) -> Self {
Self::new_generic(pdu_header, None, offset, file_data_len) Self::new(pdu_header, None, offset, file_data_len)
} }
pub fn new_generic( /// Generic constructor.
pub fn new(
mut pdu_header: PduHeader, mut pdu_header: PduHeader,
segment_metadata: Option<SegmentMetadata<'seg_meta>>, segment_metadata: Option<SegmentMetadata<'seg_meta>>,
offset: u64, offset: u64,
@@ -331,6 +381,7 @@ impl<'seg_meta> FileDataPduCreatorWithReservedDatafield<'seg_meta> {
self.common.calc_pdu_datafield_len(self.file_data_len) self.common.calc_pdu_datafield_len(self.file_data_len)
} }
/// Length of the written PDU.
pub fn len_written(&self) -> usize { pub fn len_written(&self) -> usize {
self.common.pdu_header.header_len() + self.calc_pdu_datafield_len() self.common.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
@@ -377,7 +428,7 @@ impl CfdpPdu for FileDataPduCreatorWithReservedDatafield<'_> {
} }
} }
/// This structure is created with [FileDataPduCreatorReservedDatafield::write_to_bytes_partially] /// This structure is created with [FileDataPduCreatorWithReservedDatafield::write_to_bytes_partially]
/// and provides an API to read file data from the virtual filesystem into the file data PDU buffer /// and provides an API to read file data from the virtual filesystem into the file data PDU buffer
/// directly. /// directly.
/// ///
@@ -392,6 +443,7 @@ pub struct FileDataPduCreatorWithUnwrittenData<'buf> {
} }
impl FileDataPduCreatorWithUnwrittenData<'_> { impl FileDataPduCreatorWithUnwrittenData<'_> {
/// Mutable access to the file data field.
pub fn file_data_field_mut(&mut self) -> &mut [u8] { pub fn file_data_field_mut(&mut self) -> &mut [u8] {
&mut self.write_buf[self.file_data_offset as usize &mut self.write_buf[self.file_data_offset as usize
..self.file_data_offset as usize + self.file_data_len as usize] ..self.file_data_offset as usize + self.file_data_len as usize]
@@ -419,8 +471,8 @@ pub fn calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(
segment_metadata: Option<&SegmentMetadata>, segment_metadata: Option<&SegmentMetadata>,
) -> usize { ) -> usize {
let mut subtract = pdu_header.header_len(); let mut subtract = pdu_header.header_len();
if segment_metadata.is_some() { if let Some(segment_metadata) = segment_metadata {
subtract += 1 + segment_metadata.as_ref().unwrap().metadata().unwrap().len(); subtract += 1 + segment_metadata.metadata().unwrap().len();
} }
if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large { if pdu_header.common_pdu_conf().file_flag == LargeFileFlag::Large {
subtract += 8; subtract += 8;
@@ -544,7 +596,7 @@ mod tests {
buf[written - 1] -= 1; buf[written - 1] -= 1;
let crc: u16 = ((buf[written - 2] as u16) << 8) | buf[written - 1] as u16; let crc: u16 = ((buf[written - 2] as u16) << 8) | buf[written - 1] as u16;
let error = FileDataPdu::from_bytes(&buf).unwrap_err(); let error = FileDataPdu::from_bytes(&buf).unwrap_err();
if let PduError::ChecksumError(e) = error { if let PduError::Checksum(e) = error {
assert_eq!(e, crc); assert_eq!(e, crc);
} else { } else {
panic!("expected crc error"); panic!("expected crc error");
@@ -753,7 +805,7 @@ mod tests {
assert!(pdu_reader_error.is_err()); assert!(pdu_reader_error.is_err());
let error = pdu_reader_error.unwrap_err(); let error = pdu_reader_error.unwrap_err();
match error { match error {
PduError::ChecksumError(_) => (), PduError::Checksum(_) => (),
_ => { _ => {
panic!("unexpected PDU error {}", error) panic!("unexpected PDU error {}", error)
} }
+125 -54
View File
@@ -1,35 +1,44 @@
//! # Finished PDU packet implementation.
use crate::cfdp::pdu::{ use crate::cfdp::pdu::{
add_pdu_crc, generic_length_checks_pdu_deserialization, FileDirectiveType, PduError, PduHeader, add_pdu_crc, generic_length_checks_pdu_deserialization, FileDirectiveType, PduError, PduHeader,
}; };
use crate::cfdp::tlv::{ use crate::cfdp::tlv::{
EntityIdTlv, FilestoreResponseTlv, GenericTlv, Tlv, TlvType, TlvTypeField, WritableTlv, EntityIdTlv, FilestoreResponseTlv, GenericTlv, Tlv, TlvType, TlvTypeField, WritableTlv,
}; };
use crate::cfdp::{ConditionCode, CrcFlag, Direction, PduType, TlvLvError}; use crate::cfdp::{ConditionCode, CrcFlag, Direction, PduType};
use crate::ByteConversionError; use crate::ByteConversionError;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::tlv::ReadableTlv; use super::tlv::ReadableTlv;
use super::{CfdpPdu, WritablePduPacket}; use super::{CfdpPdu, InvalidTlvTypeFieldError, WritablePduPacket};
/// Delivery code enumeration.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum DeliveryCode { pub enum DeliveryCode {
/// Completed delivery.
Complete = 0, Complete = 0,
/// Incomplete delivery.
Incomplete = 1, Incomplete = 1,
} }
/// File status enumeration.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FileStatus { pub enum FileStatus {
/// File was discarded deliberately.
DiscardDeliberately = 0b00, DiscardDeliberately = 0b00,
/// File was rejected by the filestore.
DiscardedFsRejection = 0b01, DiscardedFsRejection = 0b01,
/// File was retained (but not necesarilly complete).
Retained = 0b10, Retained = 0b10,
/// Unreported file status.
Unreported = 0b11, Unreported = 0b11,
} }
@@ -50,12 +59,12 @@ pub struct FinishedPduCreator<'fs_responses> {
impl<'fs_responses> FinishedPduCreator<'fs_responses> { impl<'fs_responses> FinishedPduCreator<'fs_responses> {
/// Default finished PDU: No error (no fault location field) and no filestore responses. /// Default finished PDU: No error (no fault location field) and no filestore responses.
pub fn new_default( pub fn new_no_error(
pdu_header: PduHeader, pdu_header: PduHeader,
delivery_code: DeliveryCode, delivery_code: DeliveryCode,
file_status: FileStatus, file_status: FileStatus,
) -> Self { ) -> Self {
Self::new_generic( Self::new(
pdu_header, pdu_header,
ConditionCode::NoError, ConditionCode::NoError,
delivery_code, delivery_code,
@@ -65,6 +74,7 @@ impl<'fs_responses> FinishedPduCreator<'fs_responses> {
) )
} }
/// Constructor where the fault location is provided.
pub fn new_with_error( pub fn new_with_error(
pdu_header: PduHeader, pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,
@@ -72,7 +82,7 @@ impl<'fs_responses> FinishedPduCreator<'fs_responses> {
file_status: FileStatus, file_status: FileStatus,
fault_location: EntityIdTlv, fault_location: EntityIdTlv,
) -> Self { ) -> Self {
Self::new_generic( Self::new(
pdu_header, pdu_header,
condition_code, condition_code,
delivery_code, delivery_code,
@@ -82,7 +92,8 @@ impl<'fs_responses> FinishedPduCreator<'fs_responses> {
) )
} }
pub fn new_generic( /// Generic constructor.
pub fn new(
mut pdu_header: PduHeader, mut pdu_header: PduHeader,
condition_code: ConditionCode, condition_code: ConditionCode,
delivery_code: DeliveryCode, delivery_code: DeliveryCode,
@@ -109,23 +120,38 @@ impl<'fs_responses> FinishedPduCreator<'fs_responses> {
finished_pdu finished_pdu
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
}
/// Condition code.
#[inline]
pub fn condition_code(&self) -> ConditionCode { pub fn condition_code(&self) -> ConditionCode {
self.condition_code self.condition_code
} }
/// Delivery code.
#[inline]
pub fn delivery_code(&self) -> DeliveryCode { pub fn delivery_code(&self) -> DeliveryCode {
self.delivery_code self.delivery_code
} }
/// File status.
#[inline]
pub fn file_status(&self) -> FileStatus { pub fn file_status(&self) -> FileStatus {
self.file_status self.file_status
} }
// If there are no filestore responses, an empty slice will be returned. /// Filestore responses as a slice.
#[inline]
pub fn filestore_responses(&self) -> &[FilestoreResponseTlv<'_, '_, '_>] { pub fn filestore_responses(&self) -> &[FilestoreResponseTlv<'_, '_, '_>] {
self.fs_responses self.fs_responses
} }
/// Optional fault location [EntityIdTlv].
#[inline]
pub fn fault_location(&self) -> Option<EntityIdTlv> { pub fn fault_location(&self) -> Option<EntityIdTlv> {
self.fault_location self.fault_location
} }
@@ -143,20 +169,9 @@ impl<'fs_responses> FinishedPduCreator<'fs_responses> {
} }
datafield_len datafield_len
} }
}
impl CfdpPdu for FinishedPduCreator<'_> { /// Write [Self] to the provided buffer and returns the written size.
fn pdu_header(&self) -> &PduHeader { pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
&self.pdu_header
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::FinishedPdu)
}
}
impl WritablePduPacket for FinishedPduCreator<'_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let expected_len = self.len_written(); let expected_len = self.len_written();
if buf.len() < expected_len { if buf.len() < expected_len {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
@@ -167,7 +182,7 @@ impl WritablePduPacket for FinishedPduCreator<'_> {
} }
let mut current_idx = self.pdu_header.write_to_bytes(buf)?; let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
buf[current_idx] = FileDirectiveType::FinishedPdu as u8; buf[current_idx] = FileDirectiveType::Finished as u8;
current_idx += 1; current_idx += 1;
buf[current_idx] = ((self.condition_code as u8) << 4) buf[current_idx] = ((self.condition_code as u8) << 4)
| ((self.delivery_code as u8) << 2) | ((self.delivery_code as u8) << 2)
@@ -185,11 +200,34 @@ impl WritablePduPacket for FinishedPduCreator<'_> {
Ok(current_idx) Ok(current_idx)
} }
fn len_written(&self) -> usize { /// Length of the written PDU in bytes.
pub fn len_written(&self) -> usize {
self.pdu_header.header_len() + self.calc_pdu_datafield_len() self.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
} }
impl CfdpPdu for FinishedPduCreator<'_> {
#[inline]
fn pdu_header(&self) -> &PduHeader {
self.pdu_header()
}
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::Finished)
}
}
impl WritablePduPacket for FinishedPduCreator<'_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
self.write_to_bytes(buf)
}
fn len_written(&self) -> usize {
self.len_written()
}
}
/// Helper structure to loop through all filestore responses of a read Finished PDU. It should be /// Helper structure to loop through all filestore responses of a read Finished PDU. It should be
/// noted that iterators in Rust are not fallible, but the TLV creation can fail, for example if /// noted that iterators in Rust are not fallible, but the TLV creation can fail, for example if
/// the raw TLV data is invalid for some reason. In that case, the iterator will yield [None] /// the raw TLV data is invalid for some reason. In that case, the iterator will yield [None]
@@ -221,6 +259,7 @@ impl<'buf> Iterator for FilestoreResponseIterator<'buf> {
} }
} }
/// Fnished PDU reader structure.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -248,13 +287,13 @@ impl<'buf> FinishedPduReader<'buf> {
let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
PduError::InvalidDirectiveType { PduError::InvalidDirectiveType {
found: buf[current_idx], found: buf[current_idx],
expected: Some(FileDirectiveType::FinishedPdu), expected: Some(FileDirectiveType::Finished),
} }
})?; })?;
if directive_type != FileDirectiveType::FinishedPdu { if directive_type != FileDirectiveType::Finished {
return Err(PduError::WrongDirectiveType { return Err(PduError::WrongDirectiveType {
found: directive_type, found: directive_type,
expected: FileDirectiveType::FinishedPdu, expected: FileDirectiveType::Finished,
}); });
} }
current_idx += 1; current_idx += 1;
@@ -276,10 +315,14 @@ impl<'buf> FinishedPduReader<'buf> {
}) })
} }
/// Raw filestore responses.
#[inline]
pub fn fs_responses_raw(&self) -> &[u8] { pub fn fs_responses_raw(&self) -> &[u8] {
self.fs_responses_raw self.fs_responses_raw
} }
/// Iterator over the filestore responses.
#[inline]
pub fn fs_responses_iter(&self) -> FilestoreResponseIterator<'_> { pub fn fs_responses_iter(&self) -> FilestoreResponseIterator<'_> {
FilestoreResponseIterator { FilestoreResponseIterator {
responses_buf: self.fs_responses_raw, responses_buf: self.fs_responses_raw,
@@ -287,22 +330,36 @@ impl<'buf> FinishedPduReader<'buf> {
} }
} }
/// Condition code.
#[inline]
pub fn condition_code(&self) -> ConditionCode { pub fn condition_code(&self) -> ConditionCode {
self.condition_code self.condition_code
} }
/// Delivery code.
#[inline]
pub fn delivery_code(&self) -> DeliveryCode { pub fn delivery_code(&self) -> DeliveryCode {
self.delivery_code self.delivery_code
} }
/// File status.
#[inline]
pub fn file_status(&self) -> FileStatus { pub fn file_status(&self) -> FileStatus {
self.file_status self.file_status
} }
/// Optional fault location [EntityIdTlv].
#[inline]
pub fn fault_location(&self) -> Option<EntityIdTlv> { pub fn fault_location(&self) -> Option<EntityIdTlv> {
self.fault_location self.fault_location
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
}
fn parse_tlv_fields( fn parse_tlv_fields(
mut current_idx: usize, mut current_idx: usize,
full_len_without_crc: usize, full_len_without_crc: usize,
@@ -332,22 +389,26 @@ impl<'buf> FinishedPduReader<'buf> {
// last TLV, everything else would break the whole handling of the packet // last TLV, everything else would break the whole handling of the packet
// TLVs. // TLVs.
if current_idx != full_len_without_crc { if current_idx != full_len_without_crc {
return Err(PduError::FormatError); return Err(PduError::Format);
} }
} else { } else {
return Err(TlvLvError::InvalidTlvTypeField { return Err(PduError::TlvLv(
found: tlv_type.into(), InvalidTlvTypeFieldError {
expected: Some(TlvType::FilestoreResponse.into()), found: tlv_type.into(),
} expected: Some(TlvType::FilestoreResponse.into()),
.into()); }
.into(),
));
} }
} }
TlvTypeField::Custom(raw) => { TlvTypeField::Custom(raw) => {
return Err(TlvLvError::InvalidTlvTypeField { return Err(PduError::TlvLv(
found: raw, InvalidTlvTypeFieldError {
expected: None, found: raw,
} expected: None,
.into()); }
.into(),
));
} }
} }
} }
@@ -356,12 +417,14 @@ impl<'buf> FinishedPduReader<'buf> {
} }
impl CfdpPdu for FinishedPduReader<'_> { impl CfdpPdu for FinishedPduReader<'_> {
#[inline]
fn pdu_header(&self) -> &PduHeader { fn pdu_header(&self) -> &PduHeader {
&self.pdu_header self.pdu_header()
} }
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> { fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::FinishedPdu) Some(FileDirectiveType::Finished)
} }
} }
@@ -402,8 +465,8 @@ mod tests {
delivery_code: DeliveryCode, delivery_code: DeliveryCode,
file_status: FileStatus, file_status: FileStatus,
) -> FinishedPduCreator<'static> { ) -> FinishedPduCreator<'static> {
let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); let pdu_header = PduHeader::new_for_file_directive(common_pdu_conf(crc_flag, fss), 0);
FinishedPduCreator::new_default(pdu_header, delivery_code, file_status) FinishedPduCreator::new_no_error(pdu_header, delivery_code, file_status)
} }
#[test] #[test]
@@ -430,7 +493,7 @@ mod tests {
assert_eq!(finished_pdu.pdu_type(), PduType::FileDirective); assert_eq!(finished_pdu.pdu_type(), PduType::FileDirective);
assert_eq!( assert_eq!(
finished_pdu.file_directive_type(), finished_pdu.file_directive_type(),
Some(FileDirectiveType::FinishedPdu) Some(FileDirectiveType::Finished)
); );
assert_eq!( assert_eq!(
finished_pdu.transmission_mode(), finished_pdu.transmission_mode(),
@@ -462,7 +525,7 @@ mod tests {
); );
verify_raw_header(finished_pdu.pdu_header(), &buf); verify_raw_header(finished_pdu.pdu_header(), &buf);
let mut current_idx = finished_pdu.pdu_header().header_len(); let mut current_idx = finished_pdu.pdu_header().header_len();
assert_eq!(buf[current_idx], FileDirectiveType::FinishedPdu as u8); assert_eq!(buf[current_idx], FileDirectiveType::Finished as u8);
current_idx += 1; current_idx += 1;
assert_eq!( assert_eq!(
(buf[current_idx] >> 4) & 0b1111, (buf[current_idx] >> 4) & 0b1111,
@@ -564,7 +627,7 @@ mod tests {
buf[written - 1] -= 1; buf[written - 1] -= 1;
let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16; let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16;
let error = FinishedPduReader::new(&buf).unwrap_err(); let error = FinishedPduReader::new(&buf).unwrap_err();
if let PduError::ChecksumError(e) = error { if let PduError::Checksum(e) = error {
assert_eq!(e, crc); assert_eq!(e, crc);
} else { } else {
panic!("expected crc error"); panic!("expected crc error");
@@ -573,8 +636,10 @@ mod tests {
#[test] #[test]
fn test_with_fault_location() { fn test_with_fault_location() {
let pdu_header = let pdu_header = PduHeader::new_for_file_directive(
PduHeader::new_no_file_data(common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal), 0); common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal),
0,
);
let finished_pdu = FinishedPduCreator::new_with_error( let finished_pdu = FinishedPduCreator::new_with_error(
pdu_header, pdu_header,
ConditionCode::NakLimitReached, ConditionCode::NakLimitReached,
@@ -586,7 +651,7 @@ mod tests {
assert_eq!(finished_pdu_vec.len(), 12); assert_eq!(finished_pdu_vec.len(), 12);
assert_eq!(finished_pdu_vec[9], TlvType::EntityId.into()); assert_eq!(finished_pdu_vec[9], TlvType::EntityId.into());
assert_eq!(finished_pdu_vec[10], 1); assert_eq!(finished_pdu_vec[10], 1);
assert_eq!(finished_pdu_vec[11], TEST_DEST_ID.value_typed()); assert_eq!(finished_pdu_vec[11], TEST_DEST_ID.value());
assert_eq!( assert_eq!(
finished_pdu.fault_location().unwrap().entity_id(), finished_pdu.fault_location().unwrap().entity_id(),
&TEST_DEST_ID.into() &TEST_DEST_ID.into()
@@ -595,8 +660,10 @@ mod tests {
#[test] #[test]
fn test_deserialization_with_fault_location() { fn test_deserialization_with_fault_location() {
let pdu_header = let pdu_header = PduHeader::new_for_file_directive(
PduHeader::new_no_file_data(common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal), 0); common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal),
0,
);
let entity_id_tlv = EntityIdTlv::new(TEST_DEST_ID.into()); let entity_id_tlv = EntityIdTlv::new(TEST_DEST_ID.into());
let finished_pdu = FinishedPduCreator::new_with_error( let finished_pdu = FinishedPduCreator::new_with_error(
pdu_header, pdu_header,
@@ -631,9 +698,11 @@ mod tests {
.unwrap(); .unwrap();
let fs_responses = &[fs_response_0, fs_response_1]; let fs_responses = &[fs_response_0, fs_response_1];
let pdu_header = let pdu_header = PduHeader::new_for_file_directive(
PduHeader::new_no_file_data(common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal), 0); common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal),
let finished_pdu = FinishedPduCreator::new_generic( 0,
);
let finished_pdu = FinishedPduCreator::new(
pdu_header, pdu_header,
ConditionCode::NakLimitReached, ConditionCode::NakLimitReached,
DeliveryCode::Incomplete, DeliveryCode::Incomplete,
@@ -666,9 +735,11 @@ mod tests {
.unwrap(); .unwrap();
let fs_responses = &[fs_response_0, fs_response_1]; let fs_responses = &[fs_response_0, fs_response_1];
let pdu_header = let pdu_header = PduHeader::new_for_file_directive(
PduHeader::new_no_file_data(common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal), 0); common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal),
let finished_pdu = FinishedPduCreator::new_generic( 0,
);
let finished_pdu = FinishedPduCreator::new(
pdu_header, pdu_header,
ConditionCode::NakLimitReached, ConditionCode::NakLimitReached,
DeliveryCode::Incomplete, DeliveryCode::Incomplete,
+88 -33
View File
@@ -1,3 +1,4 @@
//! # Metadata PDU packet implementation.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use super::tlv::TlvOwned; use super::tlv::TlvOwned;
use crate::cfdp::lv::Lv; use crate::cfdp::lv::Lv;
@@ -16,16 +17,21 @@ use serde::{Deserialize, Serialize};
use super::tlv::ReadableTlv; use super::tlv::ReadableTlv;
use super::{CfdpPdu, WritablePduPacket}; use super::{CfdpPdu, WritablePduPacket};
/// Generic metadata parameters.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MetadataGenericParams { pub struct MetadataGenericParams {
/// Closure requested flag.
pub closure_requested: bool, pub closure_requested: bool,
/// Checksum type.
pub checksum_type: ChecksumType, pub checksum_type: ChecksumType,
/// File size.
pub file_size: u64, pub file_size: u64,
} }
impl MetadataGenericParams { impl MetadataGenericParams {
/// Constructor.
pub fn new(closure_requested: bool, checksum_type: ChecksumType, file_size: u64) -> Self { pub fn new(closure_requested: bool, checksum_type: ChecksumType, file_size: u64) -> Self {
Self { Self {
closure_requested, closure_requested,
@@ -35,6 +41,7 @@ impl MetadataGenericParams {
} }
} }
/// Build the metadata options from a slice of [Tlv]s
pub fn build_metadata_opts_from_slice( pub fn build_metadata_opts_from_slice(
buf: &mut [u8], buf: &mut [u8],
tlvs: &[Tlv], tlvs: &[Tlv],
@@ -46,6 +53,7 @@ pub fn build_metadata_opts_from_slice(
Ok(written) Ok(written)
} }
/// Build the metadata options from a vector of [Tlv]s
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn build_metadata_opts_from_vec( pub fn build_metadata_opts_from_vec(
buf: &mut [u8], buf: &mut [u8],
@@ -54,6 +62,7 @@ pub fn build_metadata_opts_from_vec(
build_metadata_opts_from_slice(buf, tlvs.as_slice()) build_metadata_opts_from_slice(buf, tlvs.as_slice())
} }
/// Build the metadata options from a slice of [TlvOwned]s
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn build_metadata_opts_from_owned_slice(tlvs: &[TlvOwned]) -> Vec<u8> { pub fn build_metadata_opts_from_owned_slice(tlvs: &[TlvOwned]) -> Vec<u8> {
let mut sum_vec = Vec::new(); let mut sum_vec = Vec::new();
@@ -77,6 +86,7 @@ pub struct MetadataPduCreator<'src_name, 'dest_name, 'opts> {
} }
impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'opts> { impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'opts> {
/// Constructor for a metadata PDU without options.
pub fn new_no_opts( pub fn new_no_opts(
pdu_header: PduHeader, pdu_header: PduHeader,
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
@@ -92,6 +102,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
) )
} }
/// Constructor for a metadata PDU with options.
pub fn new_with_opts( pub fn new_with_opts(
pdu_header: PduHeader, pdu_header: PduHeader,
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
@@ -108,6 +119,7 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
) )
} }
/// Generic constructor for a metadata PDU.
pub fn new( pub fn new(
mut pdu_header: PduHeader, mut pdu_header: PduHeader,
metadata_params: MetadataGenericParams, metadata_params: MetadataGenericParams,
@@ -128,18 +140,32 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
pdu pdu
} }
/// Metadata generic parameters.
#[inline]
pub fn metadata_params(&self) -> &MetadataGenericParams { pub fn metadata_params(&self) -> &MetadataGenericParams {
&self.metadata_params &self.metadata_params
} }
/// Source file name as a [Lv].
#[inline]
pub fn src_file_name(&self) -> Lv<'src_name> { pub fn src_file_name(&self) -> Lv<'src_name> {
self.src_file_name self.src_file_name
} }
/// Destination file name as a [Lv].
#[inline]
pub fn dest_file_name(&self) -> Lv<'dest_name> { pub fn dest_file_name(&self) -> Lv<'dest_name> {
self.dest_file_name self.dest_file_name
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
}
/// Raw options.
#[inline]
pub fn options(&self) -> &'opts [u8] { pub fn options(&self) -> &'opts [u8] {
self.options self.options
} }
@@ -169,20 +195,9 @@ impl<'src_name, 'dest_name, 'opts> MetadataPduCreator<'src_name, 'dest_name, 'op
} }
len len
} }
}
impl CfdpPdu for MetadataPduCreator<'_, '_, '_> { /// Write [Self] to the provided buffer and returns the written size.
fn pdu_header(&self) -> &PduHeader { pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
&self.pdu_header
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::MetadataPdu)
}
}
impl WritablePduPacket for MetadataPduCreator<'_, '_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
let expected_len = self.len_written(); let expected_len = self.len_written();
if buf.len() < expected_len { if buf.len() < expected_len {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
@@ -193,7 +208,7 @@ impl WritablePduPacket for MetadataPduCreator<'_, '_, '_> {
} }
let mut current_idx = self.pdu_header.write_to_bytes(buf)?; let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
buf[current_idx] = FileDirectiveType::MetadataPdu as u8; buf[current_idx] = FileDirectiveType::Metadata as u8;
current_idx += 1; current_idx += 1;
buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 6) buf[current_idx] = ((self.metadata_params.closure_requested as u8) << 6)
| (self.metadata_params.checksum_type as u8); | (self.metadata_params.checksum_type as u8);
@@ -217,11 +232,34 @@ impl WritablePduPacket for MetadataPduCreator<'_, '_, '_> {
Ok(current_idx) Ok(current_idx)
} }
fn len_written(&self) -> usize { /// Length of the written PDU in bytes.
pub fn len_written(&self) -> usize {
self.pdu_header.header_len() + self.calc_pdu_datafield_len() self.pdu_header.header_len() + self.calc_pdu_datafield_len()
} }
} }
impl CfdpPdu for MetadataPduCreator<'_, '_, '_> {
#[inline]
fn pdu_header(&self) -> &PduHeader {
self.pdu_header()
}
#[inline]
fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::Metadata)
}
}
impl WritablePduPacket for MetadataPduCreator<'_, '_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
self.write_to_bytes(buf)
}
fn len_written(&self) -> usize {
self.len_written()
}
}
/// Helper structure to loop through all options of a metadata PDU. It should be noted that /// Helper structure to loop through all options of a metadata PDU. It should be noted that
/// iterators in Rust are not fallible, but the TLV creation can fail, for example if the raw TLV /// iterators in Rust are not fallible, but the TLV creation can fail, for example if the raw TLV
/// data is invalid for some reason. In that case, the iterator will yield [None] because there /// data is invalid for some reason. In that case, the iterator will yield [None] because there
@@ -271,10 +309,12 @@ pub struct MetadataPduReader<'buf> {
} }
impl<'raw> MetadataPduReader<'raw> { impl<'raw> MetadataPduReader<'raw> {
/// Constructor from raw bytes.
pub fn new(buf: &'raw [u8]) -> Result<Self, PduError> { pub fn new(buf: &'raw [u8]) -> Result<Self, PduError> {
Self::from_bytes(buf) Self::from_bytes(buf)
} }
/// Constructor from raw bytes.
pub fn from_bytes(buf: &'raw [u8]) -> Result<Self, PduError> { pub fn from_bytes(buf: &'raw [u8]) -> Result<Self, PduError> {
let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?; let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?; let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
@@ -288,13 +328,13 @@ impl<'raw> MetadataPduReader<'raw> {
let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| { let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
PduError::InvalidDirectiveType { PduError::InvalidDirectiveType {
found: buf[current_idx], found: buf[current_idx],
expected: Some(FileDirectiveType::MetadataPdu), expected: Some(FileDirectiveType::Metadata),
} }
})?; })?;
if directive_type != FileDirectiveType::MetadataPdu { if directive_type != FileDirectiveType::Metadata {
return Err(PduError::WrongDirectiveType { return Err(PduError::WrongDirectiveType {
found: directive_type, found: directive_type,
expected: FileDirectiveType::MetadataPdu, expected: FileDirectiveType::Metadata,
}); });
} }
current_idx += 1; current_idx += 1;
@@ -330,35 +370,50 @@ impl<'raw> MetadataPduReader<'raw> {
}) })
} }
/// PDU header.
#[inline]
pub fn pdu_header(&self) -> &PduHeader {
&self.pdu_header
}
/// Raw options.
#[inline]
pub fn options(&self) -> &'raw [u8] { pub fn options(&self) -> &'raw [u8] {
self.options self.options
} }
/// Generic metadata parameters.
#[inline]
pub fn metadata_params(&self) -> &MetadataGenericParams { pub fn metadata_params(&self) -> &MetadataGenericParams {
&self.metadata_params &self.metadata_params
} }
pub fn src_file_name(&self) -> Lv { /// Source file name as a [Lv].
#[inline]
pub fn src_file_name(&self) -> Lv<'_> {
self.src_file_name self.src_file_name
} }
pub fn dest_file_name(&self) -> Lv { /// Destination file name as a [Lv].
#[inline]
pub fn dest_file_name(&self) -> Lv<'_> {
self.dest_file_name self.dest_file_name
} }
} }
impl CfdpPdu for MetadataPduReader<'_> { impl CfdpPdu for MetadataPduReader<'_> {
#[inline]
fn pdu_header(&self) -> &PduHeader { fn pdu_header(&self) -> &PduHeader {
&self.pdu_header self.pdu_header()
} }
fn file_directive_type(&self) -> Option<FileDirectiveType> { fn file_directive_type(&self) -> Option<FileDirectiveType> {
Some(FileDirectiveType::MetadataPdu) Some(FileDirectiveType::Metadata)
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { mod tests {
use alloc::string::ToString; use alloc::string::ToString;
use crate::cfdp::lv::Lv; use crate::cfdp::lv::Lv;
@@ -392,7 +447,7 @@ pub mod tests {
Lv<'static>, Lv<'static>,
MetadataPduCreator<'static, 'static, '_>, MetadataPduCreator<'static, 'static, '_>,
) { ) {
let pdu_header = PduHeader::new_no_file_data(common_pdu_conf(crc_flag, fss), 0); let pdu_header = PduHeader::new_for_file_directive(common_pdu_conf(crc_flag, fss), 0);
let metadata_params = MetadataGenericParams::new(closure_requested, checksum_type, 0x1010); let metadata_params = MetadataGenericParams::new(closure_requested, checksum_type, 0x1010);
let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed"); let src_filename = Lv::new_from_str(SRC_FILENAME).expect("Generating string LV failed");
let dest_filename = let dest_filename =
@@ -441,7 +496,7 @@ pub mod tests {
); );
assert_eq!( assert_eq!(
metadata_pdu.file_directive_type(), metadata_pdu.file_directive_type(),
Some(FileDirectiveType::MetadataPdu) Some(FileDirectiveType::Metadata)
); );
assert_eq!( assert_eq!(
metadata_pdu.transmission_mode(), metadata_pdu.transmission_mode(),
@@ -472,7 +527,7 @@ pub mod tests {
+ expected_src_filename.len_full() + expected_src_filename.len_full()
+ expected_dest_filename.len_full() + expected_dest_filename.len_full()
); );
assert_eq!(buf[7], FileDirectiveType::MetadataPdu as u8); assert_eq!(buf[7], FileDirectiveType::Metadata as u8);
assert_eq!(buf[8] >> 6, closure_requested as u8); assert_eq!(buf[8] >> 6, closure_requested as u8);
assert_eq!(buf[8] & 0b1111, checksum_type as u8); assert_eq!(buf[8] & 0b1111, checksum_type as u8);
assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 0x1010); assert_eq!(u32::from_be_bytes(buf[9..13].try_into().unwrap()), 0x1010);
@@ -720,7 +775,7 @@ pub mod tests {
fn test_with_owned_opts() { fn test_with_owned_opts() {
let tlv1 = TlvOwned::new_empty(TlvType::FlowLabel); let tlv1 = TlvOwned::new_empty(TlvType::FlowLabel);
let msg_to_user: [u8; 4] = [1, 2, 3, 4]; let msg_to_user: [u8; 4] = [1, 2, 3, 4];
let tlv2 = TlvOwned::new(TlvType::MsgToUser, &msg_to_user).unwrap(); let tlv2 = TlvOwned::new(TlvType::MsgToUser, &msg_to_user);
let mut all_tlvs = tlv1.to_vec(); let mut all_tlvs = tlv1.to_vec();
all_tlvs.extend(tlv2.to_vec()); all_tlvs.extend(tlv2.to_vec());
let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu( let (src_filename, dest_filename, metadata_pdu) = generic_metadata_pdu(
@@ -777,10 +832,10 @@ pub mod tests {
let error = metadata_error.unwrap_err(); let error = metadata_error.unwrap_err();
if let PduError::InvalidDirectiveType { found, expected } = error { if let PduError::InvalidDirectiveType { found, expected } = error {
assert_eq!(found, 0xff); assert_eq!(found, 0xff);
assert_eq!(expected, Some(FileDirectiveType::MetadataPdu)); assert_eq!(expected, Some(FileDirectiveType::Metadata));
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"invalid directive type value 255, expected Some(MetadataPdu)" "invalid directive type, found 255, expected Some(Metadata)"
); );
} else { } else {
panic!("Expected InvalidDirectiveType error, got {:?}", error); panic!("Expected InvalidDirectiveType error, got {:?}", error);
@@ -797,16 +852,16 @@ pub mod tests {
&[], &[],
); );
let mut metadata_vec = metadata_pdu.to_vec().unwrap(); let mut metadata_vec = metadata_pdu.to_vec().unwrap();
metadata_vec[7] = FileDirectiveType::EofPdu as u8; metadata_vec[7] = FileDirectiveType::Eof as u8;
let metadata_error = MetadataPduReader::from_bytes(&metadata_vec); let metadata_error = MetadataPduReader::from_bytes(&metadata_vec);
assert!(metadata_error.is_err()); assert!(metadata_error.is_err());
let error = metadata_error.unwrap_err(); let error = metadata_error.unwrap_err();
if let PduError::WrongDirectiveType { found, expected } = error { if let PduError::WrongDirectiveType { found, expected } = error {
assert_eq!(found, FileDirectiveType::EofPdu); assert_eq!(found, FileDirectiveType::Eof);
assert_eq!(expected, FileDirectiveType::MetadataPdu); assert_eq!(expected, FileDirectiveType::Metadata);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"found directive type EofPdu, expected MetadataPdu" "wrong directive type, found Eof, expected Metadata"
); );
} else { } else {
panic!("Expected InvalidDirectiveType error, got {:?}", error); panic!("Expected InvalidDirectiveType error, got {:?}", error);
+142 -130
View File
@@ -1,13 +1,12 @@
//! CFDP Packet Data Unit (PDU) support. //! CFDP Packet Data Unit (PDU) support.
use crate::cfdp::pdu::ack::InvalidAckedDirectiveCodeError;
use crate::cfdp::pdu::nak::InvalidStartOrEndOfScopeError;
use crate::cfdp::*; use crate::cfdp::*;
use crate::crc::CRC_CCITT_FALSE;
use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum}; use crate::util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedEnum};
use crate::ByteConversionError; use crate::ByteConversionError;
use crate::CRC_CCITT_FALSE;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
pub mod ack; pub mod ack;
pub mod eof; pub mod eof;
@@ -16,156 +15,115 @@ pub mod finished;
pub mod metadata; pub mod metadata;
pub mod nak; pub mod nak;
/// File directive type.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FileDirectiveType { pub enum FileDirectiveType {
EofPdu = 0x04, /// EOF.
FinishedPdu = 0x05, Eof = 0x04,
AckPdu = 0x06, /// Finished.
MetadataPdu = 0x07, Finished = 0x05,
NakPdu = 0x08, /// ACK.
PromptPdu = 0x09, Ack = 0x06,
KeepAlivePdu = 0x0c, /// Metadata.
Metadata = 0x07,
/// NAK.
Nak = 0x08,
/// Prompt.
Prompt = 0x09,
/// Keep Alive.
KeepAlive = 0x0c,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// PDU error.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PduError { pub enum PduError {
ByteConversion(ByteConversionError), /// Byte conversion error.
/// Found version ID invalid, not equal to [CFDP_VERSION_2]. #[error("byte conversion error: {0}")]
ByteConversion(#[from] ByteConversionError),
/// Found version ID invalid, not equal to [super::CFDP_VERSION_2].
#[error("CFDP version missmatch, found {0}, expected {ver}", ver = super::CFDP_VERSION_2)]
CfdpVersionMissmatch(u8), CfdpVersionMissmatch(u8),
/// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported.
#[error("invalid PDU entity ID length {0}, only [1, 2, 4, 8] are allowed")]
InvalidEntityLen(u8), InvalidEntityLen(u8),
/// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported. /// Invalid length for the entity ID detected. Only the values 1, 2, 4 and 8 are supported.
#[error("invalid transaction ID length {0}")]
InvalidTransactionSeqNumLen(u8), InvalidTransactionSeqNumLen(u8),
/// Source and destination entity ID lengths do not match.
#[error(
"missmatch of PDU source ID length {src_id_len} and destination ID length {dest_id_len}"
)]
SourceDestIdLenMissmatch { SourceDestIdLenMissmatch {
/// Source ID length.
src_id_len: usize, src_id_len: usize,
/// Destination ID length.
dest_id_len: usize, dest_id_len: usize,
}, },
/// Wrong directive type, for example when parsing the directive field for a file directive /// Wrong directive type, for example when parsing the directive field for a file directive
/// PDU. /// PDU.
#[error("wrong directive type, found {found:?}, expected {expected:?}")]
WrongDirectiveType { WrongDirectiveType {
/// Found directive type.
found: FileDirectiveType, found: FileDirectiveType,
/// Expected directive type.
expected: FileDirectiveType, expected: FileDirectiveType,
}, },
/// The directive type field contained a value not in the range of permitted values. This can /// The directive type field contained a value not in the range of permitted values. This can
/// also happen if an invalid value is passed to the ACK PDU constructor. /// also happen if an invalid value is passed to the ACK PDU reader.
#[error("invalid directive type, found {found:?}, expected {expected:?}")]
InvalidDirectiveType { InvalidDirectiveType {
/// Found raw directive type.
found: u8, found: u8,
/// Expected raw directive type if applicable.
expected: Option<FileDirectiveType>, expected: Option<FileDirectiveType>,
}, },
InvalidStartOrEndOfScopeValue, /// Invalid start or end of scope for a NAK PDU.
#[error("nak pdu: {0}")]
InvalidStartOrEndOfScope(#[from] InvalidStartOrEndOfScopeError),
/// Invalid condition code. Contains the raw detected value. /// Invalid condition code. Contains the raw detected value.
#[error("invalid condition code {0}")]
InvalidConditionCode(u8), InvalidConditionCode(u8),
/// Invalid checksum type which is not part of the checksums listed in the /// Invalid checksum type which is not part of the checksums listed in the
/// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/). /// [SANA Checksum Types registry](https://sanaregistry.org/r/checksum_identifiers/).
#[error("invalid checksum type {0}")]
InvalidChecksumType(u8), InvalidChecksumType(u8),
/// File size is too large.
#[error("file size {0} too large")]
FileSizeTooLarge(u64), FileSizeTooLarge(u64),
/// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC. /// If the CRC flag for a PDU is enabled and the checksum check fails. Contains raw 16-bit CRC.
ChecksumError(u16), #[error("checksum error for checksum {0}")]
Checksum(u16),
/// Generic error for invalid PDU formats. /// Generic error for invalid PDU formats.
FormatError, #[error("generic PDU format error")]
Format,
/// Error handling a TLV field. /// Error handling a TLV field.
TlvLvError(TlvLvError), #[error("PDU error: {0}")]
TlvLv(#[from] TlvLvError),
} }
impl Display for PduError { impl From<InvalidAckedDirectiveCodeError> for PduError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { fn from(value: InvalidAckedDirectiveCodeError) -> Self {
match self { Self::InvalidDirectiveType {
PduError::InvalidEntityLen(raw_id) => { found: value.0 as u8,
write!( expected: None,
f,
"invalid PDU entity ID length {raw_id}, only [1, 2, 4, 8] are allowed"
)
}
PduError::InvalidStartOrEndOfScopeValue => {
write!(f, "invalid start or end of scope for NAK PDU")
}
PduError::InvalidTransactionSeqNumLen(raw_id) => {
write!(
f,
"invalid PDUtransaction seq num length {raw_id}, only [1, 2, 4, 8] are allowed"
)
}
PduError::CfdpVersionMissmatch(raw) => {
write!(
f,
"cfdp version missmatch, found {raw}, expected {CFDP_VERSION_2}"
)
}
PduError::SourceDestIdLenMissmatch {
src_id_len,
dest_id_len,
} => {
write!(
f,
"missmatch of PDU source length {src_id_len} and destination length {dest_id_len}"
)
}
PduError::ByteConversion(e) => {
write!(f, "{}", e)
}
PduError::FileSizeTooLarge(value) => {
write!(f, "file size value {value} exceeds allowed 32 bit width")
}
PduError::WrongDirectiveType { found, expected } => {
write!(f, "found directive type {found:?}, expected {expected:?}")
}
PduError::InvalidConditionCode(raw_code) => {
write!(f, "found invalid condition code with raw value {raw_code}")
}
PduError::InvalidDirectiveType { found, expected } => {
write!(
f,
"invalid directive type value {found}, expected {expected:?}"
)
}
PduError::InvalidChecksumType(checksum_type) => {
write!(f, "invalid checksum type {checksum_type}")
}
PduError::ChecksumError(checksum) => {
write!(f, "checksum error for CRC {checksum:#04x}")
}
PduError::TlvLvError(error) => {
write!(f, "pdu tlv error: {error}")
}
PduError::FormatError => {
write!(f, "generic PDU format error")
}
} }
} }
} }
#[cfg(feature = "std")] /// Generic trait for a PDU which can be written to bytes.
impl Error for PduError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
PduError::ByteConversion(e) => Some(e),
PduError::TlvLvError(e) => Some(e),
_ => None,
}
}
}
impl From<ByteConversionError> for PduError {
#[inline]
fn from(value: ByteConversionError) -> Self {
Self::ByteConversion(value)
}
}
impl From<TlvLvError> for PduError {
#[inline]
fn from(e: TlvLvError) -> Self {
Self::TlvLvError(e)
}
}
pub trait WritablePduPacket { pub trait WritablePduPacket {
/// Length when written to bytes.
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
/// Write the PDU to a raw buffer, returning the written length.
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError>; fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError>;
/// Convert the PDU to an owned vector of bytes.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<Vec<u8>, PduError> { fn to_vec(&self) -> Result<Vec<u8>, PduError> {
// This is the correct way to do this. See // This is the correct way to do this. See
@@ -179,48 +137,58 @@ pub trait WritablePduPacket {
/// Abstraction trait for fields and properties common for all PDUs. /// Abstraction trait for fields and properties common for all PDUs.
pub trait CfdpPdu { pub trait CfdpPdu {
/// PDU header.
fn pdu_header(&self) -> &PduHeader; fn pdu_header(&self) -> &PduHeader;
/// Source ID (file sender).
#[inline] #[inline]
fn source_id(&self) -> UnsignedByteField { fn source_id(&self) -> UnsignedByteField {
self.pdu_header().common_pdu_conf().source_entity_id self.pdu_header().common_pdu_conf().source_entity_id
} }
/// Destination ID (file sender).
#[inline] #[inline]
fn dest_id(&self) -> UnsignedByteField { fn dest_id(&self) -> UnsignedByteField {
self.pdu_header().common_pdu_conf().dest_entity_id self.pdu_header().common_pdu_conf().dest_entity_id
} }
/// Transaction sequence number.
#[inline] #[inline]
fn transaction_seq_num(&self) -> UnsignedByteField { fn transaction_seq_num(&self) -> UnsignedByteField {
self.pdu_header().common_pdu_conf().transaction_seq_num self.pdu_header().common_pdu_conf().transaction_seq_num
} }
/// Transmission mode.
#[inline] #[inline]
fn transmission_mode(&self) -> TransmissionMode { fn transmission_mode(&self) -> TransmissionMode {
self.pdu_header().common_pdu_conf().trans_mode self.pdu_header().common_pdu_conf().trans_mode
} }
/// Direction.
#[inline] #[inline]
fn direction(&self) -> Direction { fn direction(&self) -> Direction {
self.pdu_header().common_pdu_conf().direction self.pdu_header().common_pdu_conf().direction
} }
/// CRC flag.
#[inline] #[inline]
fn crc_flag(&self) -> CrcFlag { fn crc_flag(&self) -> CrcFlag {
self.pdu_header().common_pdu_conf().crc_flag self.pdu_header().common_pdu_conf().crc_flag
} }
/// File flag.
#[inline] #[inline]
fn file_flag(&self) -> LargeFileFlag { fn file_flag(&self) -> LargeFileFlag {
self.pdu_header().common_pdu_conf().file_flag self.pdu_header().common_pdu_conf().file_flag
} }
/// PDU type.
#[inline] #[inline]
fn pdu_type(&self) -> PduType { fn pdu_type(&self) -> PduType {
self.pdu_header().pdu_type() self.pdu_header().pdu_type()
} }
/// File directive type when applicable.
fn file_directive_type(&self) -> Option<FileDirectiveType>; fn file_directive_type(&self) -> Option<FileDirectiveType>;
} }
@@ -236,15 +204,21 @@ pub trait CfdpPdu {
pub struct CommonPduConfig { pub struct CommonPduConfig {
source_entity_id: UnsignedByteField, source_entity_id: UnsignedByteField,
dest_entity_id: UnsignedByteField, dest_entity_id: UnsignedByteField,
/// Transaction sequence number.
pub transaction_seq_num: UnsignedByteField, pub transaction_seq_num: UnsignedByteField,
/// Transmission mode.
pub trans_mode: TransmissionMode, pub trans_mode: TransmissionMode,
/// File flag.
pub file_flag: LargeFileFlag, pub file_flag: LargeFileFlag,
/// CRC flag.
pub crc_flag: CrcFlag, pub crc_flag: CrcFlag,
/// Direction.
pub direction: Direction, pub direction: Direction,
} }
// TODO: Builder pattern might be applicable here.. // TODO: Builder pattern might be applicable here..
impl CommonPduConfig { impl CommonPduConfig {
/// Generic constructor.
#[inline] #[inline]
pub fn new( pub fn new(
source_id: impl Into<UnsignedByteField>, source_id: impl Into<UnsignedByteField>,
@@ -277,6 +251,7 @@ impl CommonPduConfig {
}) })
} }
/// Constructor for custom byte field with default field values for the other fields.
#[inline] #[inline]
pub fn new_with_byte_fields( pub fn new_with_byte_fields(
source_id: impl Into<UnsignedByteField>, source_id: impl Into<UnsignedByteField>,
@@ -294,6 +269,7 @@ impl CommonPduConfig {
) )
} }
/// Source ID (file sender).
#[inline] #[inline]
pub fn source_id(&self) -> UnsignedByteField { pub fn source_id(&self) -> UnsignedByteField {
self.source_entity_id self.source_entity_id
@@ -322,6 +298,7 @@ impl CommonPduConfig {
Ok((source_id, dest_id)) Ok((source_id, dest_id))
} }
/// Set the source and destination ID field.
#[inline] #[inline]
pub fn set_source_and_dest_id( pub fn set_source_and_dest_id(
&mut self, &mut self,
@@ -334,6 +311,7 @@ impl CommonPduConfig {
Ok(()) Ok(())
} }
/// Destination ID (file receiver).
#[inline] #[inline]
pub fn dest_id(&self) -> UnsignedByteField { pub fn dest_id(&self) -> UnsignedByteField {
self.dest_entity_id self.dest_entity_id
@@ -372,6 +350,7 @@ impl PartialEq for CommonPduConfig {
} }
} }
/// Fixed header length of the PDU header.
pub const FIXED_HEADER_LEN: usize = 4; pub const FIXED_HEADER_LEN: usize = 4;
/// Abstraction for the PDU header common to all CFDP PDUs. /// Abstraction for the PDU header common to all CFDP PDUs.
@@ -389,6 +368,10 @@ pub struct PduHeader {
} }
impl PduHeader { impl PduHeader {
/// Fixed length of the PDU header when written to a raw buffer.
pub const FIXED_LEN: usize = FIXED_HEADER_LEN;
/// Constructor for a File Data PDU header.
#[inline] #[inline]
pub fn new_for_file_data( pub fn new_for_file_data(
pdu_conf: CommonPduConfig, pdu_conf: CommonPduConfig,
@@ -405,6 +388,7 @@ impl PduHeader {
) )
} }
/// Constructor for a file data PDU.
#[inline] #[inline]
pub fn new_for_file_data_default(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { pub fn new_for_file_data_default(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self {
Self::new_generic( Self::new_generic(
@@ -415,8 +399,10 @@ impl PduHeader {
SegmentationControl::NoRecordBoundaryPreservation, SegmentationControl::NoRecordBoundaryPreservation,
) )
} }
/// Constructor for a file directive PDU.
#[inline] #[inline]
pub fn new_no_file_data(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self { pub fn new_for_file_directive(pdu_conf: CommonPduConfig, pdu_datafield_len: u16) -> Self {
Self::new_generic( Self::new_generic(
PduType::FileDirective, PduType::FileDirective,
pdu_conf, pdu_conf,
@@ -426,6 +412,19 @@ impl PduHeader {
) )
} }
/// Constructor from a given [CommonPduConfig] and for a file directive PDU.
#[inline]
pub fn from_pdu_conf_for_file_directive(pdu_conf: CommonPduConfig) -> Self {
Self::new_generic(
PduType::FileDirective,
pdu_conf,
0,
SegmentMetadataFlag::NotPresent,
SegmentationControl::NoRecordBoundaryPreservation,
)
}
/// Generic constructor.
#[inline] #[inline]
pub fn new_generic( pub fn new_generic(
pdu_type: PduType, pdu_type: PduType,
@@ -452,6 +451,7 @@ impl PduHeader {
+ self.pdu_conf.dest_entity_id.size() + self.pdu_conf.dest_entity_id.size()
} }
/// PDU data field length.
#[inline] #[inline]
pub fn pdu_datafield_len(&self) -> usize { pub fn pdu_datafield_len(&self) -> usize {
self.pdu_datafield_len.into() self.pdu_datafield_len.into()
@@ -464,15 +464,15 @@ impl PduHeader {
self.header_len() + self.pdu_datafield_len as usize self.header_len() + self.pdu_datafield_len as usize
} }
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> { /// Write the header to a raw buffer, returning the written length on success.
// Internal note: There is currently no way to pass a PDU configuration like this, but pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
// this check is still kept for defensive programming. // The API does not allow passing entity IDs with different sizes, so this should
if self.pdu_conf.source_entity_id.size() != self.pdu_conf.dest_entity_id.size() { // never happen.
return Err(PduError::SourceDestIdLenMissmatch { assert_eq!(
src_id_len: self.pdu_conf.source_entity_id.size(), self.pdu_conf.source_entity_id.size(),
dest_id_len: self.pdu_conf.dest_entity_id.size(), self.pdu_conf.dest_entity_id.size(),
}); "unexpected missmatch of source and destination entity ID length"
} );
if buf.len() if buf.len()
< FIXED_HEADER_LEN < FIXED_HEADER_LEN
+ self.pdu_conf.source_entity_id.size() + self.pdu_conf.source_entity_id.size()
@@ -481,8 +481,7 @@ impl PduHeader {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(), found: buf.len(),
expected: FIXED_HEADER_LEN, expected: FIXED_HEADER_LEN,
} });
.into());
} }
let mut current_idx = 0; let mut current_idx = 0;
buf[current_idx] = (CFDP_VERSION_2 << 5) buf[current_idx] = (CFDP_VERSION_2 << 5)
@@ -532,7 +531,7 @@ impl PduHeader {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&buf[..self.pdu_len()]); digest.update(&buf[..self.pdu_len()]);
if digest.finalize() != 0 { if digest.finalize() != 0 {
return Err(PduError::ChecksumError(u16::from_be_bytes( return Err(PduError::Checksum(u16::from_be_bytes(
buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(), buf[self.pdu_len() - 2..self.pdu_len()].try_into().unwrap(),
))); )));
} }
@@ -633,20 +632,25 @@ impl PduHeader {
)) ))
} }
/// PDU type.
#[inline] #[inline]
pub fn pdu_type(&self) -> PduType { pub fn pdu_type(&self) -> PduType {
self.pdu_type self.pdu_type
} }
/// Common PDU configuration fields.
#[inline] #[inline]
pub fn common_pdu_conf(&self) -> &CommonPduConfig { pub fn common_pdu_conf(&self) -> &CommonPduConfig {
&self.pdu_conf &self.pdu_conf
} }
/// Segment metadata flag.
#[inline]
pub fn seg_metadata_flag(&self) -> SegmentMetadataFlag { pub fn seg_metadata_flag(&self) -> SegmentMetadataFlag {
self.seg_metadata_flag self.seg_metadata_flag
} }
/// Segmentation Control.
#[inline] #[inline]
pub fn seg_ctrl(&self) -> SegmentationControl { pub fn seg_ctrl(&self) -> SegmentationControl {
self.seg_ctrl self.seg_ctrl
@@ -812,7 +816,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
assert_eq!(pdu_header.pdu_type(), PduType::FileDirective); assert_eq!(pdu_header.pdu_type(), PduType::FileDirective);
let common_conf_ref = pdu_header.common_pdu_conf(); let common_conf_ref = pdu_header.common_pdu_conf();
assert_eq!(*common_conf_ref, common_pdu_cfg); assert_eq!(*common_conf_ref, common_pdu_cfg);
@@ -878,7 +882,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
let mut buf: [u8; 7] = [0; 7]; let mut buf: [u8; 7] = [0; 7];
let res = pdu_header.write_to_bytes(&mut buf); let res = pdu_header.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -895,7 +899,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
let mut buf: [u8; 7] = [0; 7]; let mut buf: [u8; 7] = [0; 7];
let res = pdu_header.write_to_bytes(&mut buf); let res = pdu_header.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -968,7 +972,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
let mut buf: [u8; 7] = [0; 7]; let mut buf: [u8; 7] = [0; 7];
let res = pdu_header.write_to_bytes(&mut buf); let res = pdu_header.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -981,7 +985,7 @@ mod tests {
assert_eq!(raw_version, CFDP_VERSION_2 + 1); assert_eq!(raw_version, CFDP_VERSION_2 + 1);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"cfdp version missmatch, found 2, expected 1" "CFDP version missmatch, found 2, expected 1"
); );
} else { } else {
panic!("invalid exception: {}", error); panic!("invalid exception: {}", error);
@@ -1013,7 +1017,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
let mut buf: [u8; 7] = [0; 7]; let mut buf: [u8; 7] = [0; 7];
let res = pdu_header.write_to_bytes(&mut buf); let res = pdu_header.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -1029,7 +1033,7 @@ mod tests {
assert_eq!(expected, 7); assert_eq!(expected, 7);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"source slice with size 6 too small, expected at least 7 bytes" "byte conversion error: source slice with size 6 too small, expected at least 7 bytes"
); );
} }
} }
@@ -1084,7 +1088,7 @@ mod tests {
assert_eq!(dest_id_len, 2); assert_eq!(dest_id_len, 2);
assert_eq!( assert_eq!(
error.to_string(), error.to_string(),
"missmatch of PDU source length 1 and destination length 2" "missmatch of PDU source ID length 1 and destination ID length 2"
); );
} }
} }
@@ -1096,7 +1100,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
let mut buf: [u8; 7] = [0; 7]; let mut buf: [u8; 7] = [0; 7];
let res = pdu_header.write_to_bytes(&mut buf); let res = pdu_header.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -1120,7 +1124,7 @@ mod tests {
let transaction_id = UnsignedByteFieldU8::new(3); let transaction_id = UnsignedByteFieldU8::new(3);
let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id) let common_pdu_cfg = CommonPduConfig::new_with_byte_fields(src_id, dest_id, transaction_id)
.expect("common config creation failed"); .expect("common config creation failed");
let pdu_header = PduHeader::new_no_file_data(common_pdu_cfg, 5); let pdu_header = PduHeader::new_for_file_directive(common_pdu_cfg, 5);
let mut buf: [u8; 7] = [0; 7]; let mut buf: [u8; 7] = [0; 7];
let res = pdu_header.write_to_bytes(&mut buf); let res = pdu_header.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -1152,4 +1156,12 @@ mod tests {
let common_pdu_cfg_1 = common_pdu_cfg_0; let common_pdu_cfg_1 = common_pdu_cfg_0;
assert_eq!(common_pdu_cfg_0, common_pdu_cfg_1); assert_eq!(common_pdu_cfg_0, common_pdu_cfg_1);
} }
#[test]
fn test_ctor_from_pdu_conf() {
assert_eq!(
PduHeader::from_pdu_conf_for_file_directive(CommonPduConfig::default()),
PduHeader::new_for_file_directive(CommonPduConfig::default(), 0)
);
}
} }
+823 -236
View File
File diff suppressed because it is too large Load Diff
+203 -78
View File
@@ -15,17 +15,21 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::TlvLvDataTooLarge; use super::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError};
pub mod msg_to_user; pub mod msg_to_user;
/// Minimum length of a type-length-value structure, including type and length fields.
pub const MIN_TLV_LEN: usize = 2; pub const MIN_TLV_LEN: usize = 2;
/// Trait for generic TLV structures.
pub trait GenericTlv { pub trait GenericTlv {
/// TLV type field.
fn tlv_type_field(&self) -> TlvTypeField; fn tlv_type_field(&self) -> TlvTypeField;
/// Checks whether the type field contains one of the standard types specified in the CFDP /// Checks whether the type field contains one of the standard types specified in the CFDP
/// standard and is part of the [TlvType] enum. /// standard and is part of the [TlvType] enum.
#[inline]
fn is_standard_tlv(&self) -> bool { fn is_standard_tlv(&self) -> bool {
if let TlvTypeField::Standard(_) = self.tlv_type_field() { if let TlvTypeField::Standard(_) = self.tlv_type_field() {
return true; return true;
@@ -34,6 +38,7 @@ pub trait GenericTlv {
} }
/// Returns the standard TLV type if the TLV field is not a custom field /// Returns the standard TLV type if the TLV field is not a custom field
#[inline]
fn tlv_type(&self) -> Option<TlvType> { fn tlv_type(&self) -> Option<TlvType> {
if let TlvTypeField::Standard(tlv_type) = self.tlv_type_field() { if let TlvTypeField::Standard(tlv_type) = self.tlv_type_field() {
Some(tlv_type) Some(tlv_type)
@@ -43,29 +48,40 @@ pub trait GenericTlv {
} }
} }
/// Readable TLV structure trait.
pub trait ReadableTlv { pub trait ReadableTlv {
/// Value field of the TLV.
fn value(&self) -> &[u8]; fn value(&self) -> &[u8];
/// Checks whether the value field is empty. /// Checks whether the value field is empty.
#[inline]
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.value().is_empty() self.value().is_empty()
} }
/// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of /// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of
/// [Self::value] /// [Self::value]
#[inline]
fn len_value(&self) -> usize { fn len_value(&self) -> usize {
self.value().len() self.value().len()
} }
/// Returns the full raw length, including the length byte. /// Returns the full raw length, including the length byte.
#[inline]
fn len_full(&self) -> usize { fn len_full(&self) -> usize {
self.len_value() + 2 self.len_value() + 2
} }
} }
/// Writable TLV structure trait.
pub trait WritableTlv { pub trait WritableTlv {
/// Write the TLV to bytes.
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
/// Length of the written TLV.
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
/// Convenience method to write the TLV to an owned [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Vec<u8> { fn to_vec(&self) -> Vec<u8> {
let mut buf = vec![0; self.len_written()]; let mut buf = vec![0; self.len_written()];
@@ -74,34 +90,50 @@ pub trait WritableTlv {
} }
} }
/// TLV type.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum TlvType { pub enum TlvType {
/// Filestore request.
FilestoreRequest = 0x00, FilestoreRequest = 0x00,
/// Filestore response.
FilestoreResponse = 0x01, FilestoreResponse = 0x01,
/// Message to user.
MsgToUser = 0x02, MsgToUser = 0x02,
/// Fault handler.
FaultHandler = 0x04, FaultHandler = 0x04,
/// Flow label.
FlowLabel = 0x05, FlowLabel = 0x05,
/// Entity ID.
EntityId = 0x06, EntityId = 0x06,
} }
/// TLV type field variants.
///
/// This allows specifying custom variants as well.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TlvTypeField { pub enum TlvTypeField {
/// Standard TLV types.
Standard(TlvType), Standard(TlvType),
/// Custom TLV type.
Custom(u8), Custom(u8),
} }
/// Filestore action codes as specified in the standard.
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum FilestoreActionCode { pub enum FilestoreActionCode {
/// Create file.
CreateFile = 0b0000, CreateFile = 0b0000,
/// Delete file.
DeleteFile = 0b0001, DeleteFile = 0b0001,
/// Rename file.
RenameFile = 0b0010, RenameFile = 0b0010,
/// This operation appends one file to another. The first specified name will form the first /// This operation appends one file to another. The first specified name will form the first
/// part of the new file and the name of the new file. This function can be used to get /// part of the new file and the name of the new file. This function can be used to get
@@ -110,9 +142,13 @@ pub enum FilestoreActionCode {
/// This operation replaces the content of the first specified file with the content of /// This operation replaces the content of the first specified file with the content of
/// the secondly specified file. /// the secondly specified file.
ReplaceFile = 0b0100, ReplaceFile = 0b0100,
/// Create directory.
CreateDirectory = 0b0101, CreateDirectory = 0b0101,
/// Remove directory.
RemoveDirectory = 0b0110, RemoveDirectory = 0b0110,
/// Deny file.
DenyFile = 0b0111, DenyFile = 0b0111,
/// Deny directory.
DenyDirectory = 0b1000, DenyDirectory = 0b1000,
} }
@@ -153,14 +189,22 @@ pub struct Tlv<'data> {
} }
impl<'data> Tlv<'data> { impl<'data> Tlv<'data> {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv, TlvLvDataTooLarge> { /// Minimum length of a TLV structure, including type and length fields.
pub const MIN_LEN: usize = MIN_TLV_LEN;
/// Generic constructor for a TLV structure.
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Tlv<'_>, TlvLvDataTooLargeError> {
Ok(Tlv { Ok(Tlv {
tlv_type_field: TlvTypeField::Standard(tlv_type), tlv_type_field: TlvTypeField::Standard(tlv_type),
lv: Lv::new(data)?, lv: Lv::new(data)?,
}) })
} }
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Tlv, TlvLvDataTooLarge> { /// Constructor for a TLV with a custom type field.
pub fn new_with_custom_type(
tlv_type: u8,
data: &[u8],
) -> Result<Tlv<'_>, TlvLvDataTooLargeError> {
Ok(Tlv { Ok(Tlv {
tlv_type_field: TlvTypeField::Custom(tlv_type), tlv_type_field: TlvTypeField::Custom(tlv_type),
lv: Lv::new(data)?, lv: Lv::new(data)?,
@@ -179,7 +223,7 @@ impl<'data> Tlv<'data> {
/// bytestream with the exact size of the expected TLV. This function will take care /// bytestream with the exact size of the expected TLV. This function will take care
/// of parsing the length byte, and the length of the parsed TLV can be retrieved using /// of parsing the length byte, and the length of the parsed TLV can be retrieved using
/// [Self::len_full]. /// [Self::len_full].
pub fn from_bytes(buf: &'data [u8]) -> Result<Tlv<'data>, TlvLvError> { pub fn from_bytes(buf: &'data [u8]) -> Result<Tlv<'data>, ByteConversionError> {
generic_len_check_deserialization(buf, MIN_TLV_LEN)?; generic_len_check_deserialization(buf, MIN_TLV_LEN)?;
let mut tlv = Self { let mut tlv = Self {
tlv_type_field: TlvTypeField::from(buf[0]), tlv_type_field: TlvTypeField::from(buf[0]),
@@ -193,10 +237,12 @@ impl<'data> Tlv<'data> {
/// If the TLV was generated from a raw bytestream using [Self::from_bytes], the raw start /// If the TLV was generated from a raw bytestream using [Self::from_bytes], the raw start
/// of the TLV can be retrieved with this method. /// of the TLV can be retrieved with this method.
#[inline]
pub fn raw_data(&self) -> Option<&[u8]> { pub fn raw_data(&self) -> Option<&[u8]> {
self.lv.raw_data() self.lv.raw_data()
} }
/// Converts to an owned TLV variant, allocating memory for the value field.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned { pub fn to_owned(&self) -> TlvOwned {
TlvOwned { TlvOwned {
@@ -226,21 +272,23 @@ impl WritableTlv for Tlv<'_> {
self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]); self.lv.write_to_be_bytes_no_len_check(&mut buf[1..]);
Ok(self.len_full()) Ok(self.len_full())
} }
#[inline]
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.len_full() self.len_full()
} }
} }
impl GenericTlv for Tlv<'_> { impl GenericTlv for Tlv<'_> {
#[inline]
fn tlv_type_field(&self) -> TlvTypeField { fn tlv_type_field(&self) -> TlvTypeField {
self.tlv_type_field self.tlv_type_field
} }
} }
/// Component of the TLV module which require [alloc] support.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub mod alloc_mod { pub mod alloc_mod {
use crate::cfdp::TlvLvDataTooLarge;
use super::*; use super::*;
/// Owned variant of [Tlv] which is consequently [Clone]able and does not have a lifetime /// Owned variant of [Tlv] which is consequently [Clone]able and does not have a lifetime
@@ -254,24 +302,20 @@ pub mod alloc_mod {
} }
impl TlvOwned { impl TlvOwned {
pub fn new(tlv_type: TlvType, data: &[u8]) -> Result<Self, TlvLvDataTooLarge> { /// Generic constructor.
if data.len() > u8::MAX as usize { pub fn new(tlv_type: TlvType, data: &[u8]) -> Self {
return Err(TlvLvDataTooLarge(data.len())); Self {
}
Ok(Self {
tlv_type_field: TlvTypeField::Standard(tlv_type), tlv_type_field: TlvTypeField::Standard(tlv_type),
data: data.to_vec(), data: data.to_vec(),
}) }
} }
pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Result<Self, TlvLvDataTooLarge> { /// Generic constructor with a custom TLV type.
if data.len() > u8::MAX as usize { pub fn new_with_custom_type(tlv_type: u8, data: &[u8]) -> Self {
return Err(TlvLvDataTooLarge(data.len())); Self {
}
Ok(Self {
tlv_type_field: TlvTypeField::Custom(tlv_type), tlv_type_field: TlvTypeField::Custom(tlv_type),
data: data.to_vec(), data: data.to_vec(),
}) }
} }
/// Creates a TLV with an empty value field. /// Creates a TLV with an empty value field.
@@ -282,6 +326,21 @@ pub mod alloc_mod {
} }
} }
/// Write to a byte slice.
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
generic_len_check_data_serialization(buf, self.data.len(), MIN_TLV_LEN)?;
buf[0] = self.tlv_type_field.into();
buf[1] = self.data.len() as u8;
buf[2..2 + self.data.len()].copy_from_slice(&self.data);
Ok(self.len_written())
}
#[inline]
fn len_written(&self) -> usize {
self.data.len() + 2
}
/// Convert to [Tlv]
pub fn as_tlv(&self) -> Tlv<'_> { pub fn as_tlv(&self) -> Tlv<'_> {
Tlv { Tlv {
tlv_type_field: self.tlv_type_field, tlv_type_field: self.tlv_type_field,
@@ -293,6 +352,7 @@ pub mod alloc_mod {
} }
impl ReadableTlv for TlvOwned { impl ReadableTlv for TlvOwned {
#[inline]
fn value(&self) -> &[u8] { fn value(&self) -> &[u8] {
&self.data &self.data
} }
@@ -300,19 +360,17 @@ pub mod alloc_mod {
impl WritableTlv for TlvOwned { impl WritableTlv for TlvOwned {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
generic_len_check_data_serialization(buf, self.data.len(), MIN_TLV_LEN)?; self.write_to_bytes(buf)
buf[0] = self.tlv_type_field.into();
buf[1] = self.data.len() as u8;
buf[2..2 + self.data.len()].copy_from_slice(&self.data);
Ok(self.len_written())
} }
#[inline]
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.data.len() + 2 self.len_written()
} }
} }
impl GenericTlv for TlvOwned { impl GenericTlv for TlvOwned {
#[inline]
fn tlv_type_field(&self) -> TlvTypeField { fn tlv_type_field(&self) -> TlvTypeField {
self.tlv_type_field self.tlv_type_field
} }
@@ -331,6 +389,7 @@ pub mod alloc_mod {
} }
} }
/// Entity ID TLV.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -339,11 +398,13 @@ pub struct EntityIdTlv {
} }
impl EntityIdTlv { impl EntityIdTlv {
/// Constructor.
#[inline]
pub fn new(entity_id: UnsignedByteField) -> Self { pub fn new(entity_id: UnsignedByteField) -> Self {
Self { entity_id } Self { entity_id }
} }
fn check_min_len(buf: &[u8]) -> Result<(), ByteConversionError> { fn len_check(buf: &[u8]) -> Result<(), ByteConversionError> {
if buf.len() < 2 { if buf.len() < 2 {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(), found: buf.len(),
@@ -353,20 +414,27 @@ impl EntityIdTlv {
Ok(()) Ok(())
} }
/// Entity ID.
#[inline]
pub fn entity_id(&self) -> &UnsignedByteField { pub fn entity_id(&self) -> &UnsignedByteField {
&self.entity_id &self.entity_id
} }
/// Length of the value field.
#[inline]
pub fn len_value(&self) -> usize { pub fn len_value(&self) -> usize {
self.entity_id.size() self.entity_id.size()
} }
/// Full length of the TLV, including type and length fields.
#[inline]
pub fn len_full(&self) -> usize { pub fn len_full(&self) -> usize {
2 + self.entity_id.size() 2 + self.entity_id.size()
} }
/// Create from a raw bytestream.
pub fn from_bytes(buf: &[u8]) -> Result<Self, TlvLvError> { pub fn from_bytes(buf: &[u8]) -> Result<Self, TlvLvError> {
Self::check_min_len(buf)?; Self::len_check(buf)?;
verify_tlv_type(buf[0], TlvType::EntityId)?; verify_tlv_type(buf[0], TlvType::EntityId)?;
let len = buf[1]; let len = buf[1];
if len != 1 && len != 2 && len != 4 && len != 8 { if len != 1 && len != 2 && len != 4 && len != 8 {
@@ -377,65 +445,66 @@ impl EntityIdTlv {
Ok(Self { entity_id }) Ok(Self { entity_id })
} }
/// Convert to a generic [Tlv], which also erases the type information. /// Convert to a generic [Tlv], which also erases the programmatic type information.
pub fn to_tlv(self, buf: &mut [u8]) -> Result<Tlv, ByteConversionError> { pub fn to_tlv(self, buf: &mut [u8]) -> Result<Tlv<'_>, ByteConversionError> {
Self::check_min_len(buf)?; Self::len_check(buf)?;
self.entity_id self.entity_id
.write_to_be_bytes(&mut buf[2..2 + self.entity_id.size()])?; .write_to_be_bytes(&mut buf[2..2 + self.entity_id.size()])?;
if buf.len() < self.len_value() { // Can't fail.
return Err(ByteConversionError::ToSliceTooSmall {
found: buf.len(),
expected: self.len_value(),
});
}
// We performed all checks necessary to ensure this call never panics.
Ok(Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.size()]).unwrap()) Ok(Tlv::new(TlvType::EntityId, &buf[2..2 + self.entity_id.size()]).unwrap())
} }
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
// Unwrap is okay here, entity ID should never be larger than maximum allowed size.
TlvOwned::new(TlvType::EntityId, &self.entity_id.to_vec()).unwrap()
}
}
impl WritableTlv for EntityIdTlv {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
Self::check_min_len(buf)?; Self::len_check(buf)?;
buf[0] = TlvType::EntityId as u8; buf[0] = TlvType::EntityId as u8;
buf[1] = self.entity_id.size() as u8; buf[1] = self.entity_id.size() as u8;
Ok(2 + self.entity_id.write_to_be_bytes(&mut buf[2..])?) Ok(2 + self.entity_id.write_to_be_bytes(&mut buf[2..])?)
} }
#[inline]
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.len_full() self.len_full()
} }
} }
impl WritableTlv for EntityIdTlv {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.write_to_bytes(buf)
}
#[inline]
fn len_written(&self) -> usize {
self.len_written()
}
}
impl GenericTlv for EntityIdTlv { impl GenericTlv for EntityIdTlv {
#[inline]
fn tlv_type_field(&self) -> TlvTypeField { fn tlv_type_field(&self) -> TlvTypeField {
TlvTypeField::Standard(TlvType::EntityId) TlvTypeField::Standard(TlvType::EntityId)
} }
} }
impl<'data> TryFrom<Tlv<'data>> for EntityIdTlv { impl TryFrom<Tlv<'_>> for EntityIdTlv {
type Error = TlvLvError; type Error = TlvLvError;
fn try_from(value: Tlv) -> Result<Self, Self::Error> { fn try_from(value: Tlv) -> Result<Self, TlvLvError> {
match value.tlv_type_field { match value.tlv_type_field {
TlvTypeField::Standard(tlv_type) => { TlvTypeField::Standard(tlv_type) => {
if tlv_type != TlvType::EntityId { if tlv_type != TlvType::EntityId {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: tlv_type as u8, found: tlv_type as u8,
expected: Some(TlvType::EntityId as u8), expected: Some(TlvType::EntityId as u8),
}); }
.into());
} }
} }
TlvTypeField::Custom(val) => { TlvTypeField::Custom(val) => {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: val, found: val,
expected: Some(TlvType::EntityId as u8), expected: Some(TlvType::EntityId as u8),
}); }
.into());
} }
} }
let len_value = value.value().len(); let len_value = value.value().len();
@@ -455,6 +524,8 @@ impl<'data> TryFrom<Tlv<'data>> for EntityIdTlv {
} }
} }
/// Does the [FilestoreActionCode] have a second filename?
#[inline]
pub fn fs_request_has_second_filename(action_code: FilestoreActionCode) -> bool { pub fn fs_request_has_second_filename(action_code: FilestoreActionCode) -> bool {
if action_code == FilestoreActionCode::RenameFile if action_code == FilestoreActionCode::RenameFile
|| action_code == FilestoreActionCode::AppendFile || action_code == FilestoreActionCode::AppendFile
@@ -477,6 +548,7 @@ struct FilestoreTlvBase<'first_name, 'second_name> {
} }
impl FilestoreTlvBase<'_, '_> { impl FilestoreTlvBase<'_, '_> {
#[inline]
fn base_len_value(&self) -> usize { fn base_len_value(&self) -> usize {
let mut len = 1 + self.first_name.len_full(); let mut len = 1 + self.first_name.len_full();
if let Some(second_name) = self.second_name { if let Some(second_name) = self.second_name {
@@ -486,6 +558,7 @@ impl FilestoreTlvBase<'_, '_> {
} }
} }
/// Filestore request TLV.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FilestoreRequestTlv<'first_name, 'second_name> { pub struct FilestoreRequestTlv<'first_name, 'second_name> {
@@ -494,14 +567,17 @@ pub struct FilestoreRequestTlv<'first_name, 'second_name> {
} }
impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> { impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
/// Constructor for file creation.
pub fn new_create_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> { pub fn new_create_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
Self::new(FilestoreActionCode::CreateFile, file_name, None) Self::new(FilestoreActionCode::CreateFile, file_name, None)
} }
/// Constructor for file deletion.
pub fn new_delete_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> { pub fn new_delete_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
Self::new(FilestoreActionCode::DeleteFile, file_name, None) Self::new(FilestoreActionCode::DeleteFile, file_name, None)
} }
/// Constructor for file renaming.
pub fn new_rename_file( pub fn new_rename_file(
source_name: Lv<'first_name>, source_name: Lv<'first_name>,
target_name: Lv<'second_name>, target_name: Lv<'second_name>,
@@ -541,18 +617,22 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
) )
} }
/// Constructor for directory creation.
pub fn new_create_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> { pub fn new_create_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
Self::new(FilestoreActionCode::CreateDirectory, dir_name, None) Self::new(FilestoreActionCode::CreateDirectory, dir_name, None)
} }
/// Constructor for directory removal.
pub fn new_remove_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> { pub fn new_remove_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
Self::new(FilestoreActionCode::RemoveDirectory, dir_name, None) Self::new(FilestoreActionCode::RemoveDirectory, dir_name, None)
} }
/// Constructor for file denial.
pub fn new_deny_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> { pub fn new_deny_file(file_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
Self::new(FilestoreActionCode::DenyFile, file_name, None) Self::new(FilestoreActionCode::DenyFile, file_name, None)
} }
/// Constructor for directory denial.
pub fn new_deny_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> { pub fn new_deny_directory(dir_name: Lv<'first_name>) -> Result<Self, TlvLvError> {
Self::new(FilestoreActionCode::DenyDirectory, dir_name, None) Self::new(FilestoreActionCode::DenyDirectory, dir_name, None)
} }
@@ -586,26 +666,37 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
}) })
} }
/// Action code.
#[inline]
pub fn action_code(&self) -> FilestoreActionCode { pub fn action_code(&self) -> FilestoreActionCode {
self.base.action_code self.base.action_code
} }
/// First name as [Lv].
#[inline]
pub fn first_name(&self) -> Lv<'first_name> { pub fn first_name(&self) -> Lv<'first_name> {
self.base.first_name self.base.first_name
} }
/// First name as optional [Lv].
#[inline]
pub fn second_name(&self) -> Option<Lv<'second_name>> { pub fn second_name(&self) -> Option<Lv<'second_name>> {
self.base.second_name self.base.second_name
} }
/// Length of the value field.
#[inline]
pub fn len_value(&self) -> usize { pub fn len_value(&self) -> usize {
self.base.base_len_value() self.base.base_len_value()
} }
/// Full TLV length.
#[inline]
pub fn len_full(&self) -> usize { pub fn len_full(&self) -> usize {
2 + self.len_value() 2 + self.len_value()
} }
/// Construct from a raw bytestream.
pub fn from_bytes<'longest: 'first_name + 'second_name>( pub fn from_bytes<'longest: 'first_name + 'second_name>(
buf: &'longest [u8], buf: &'longest [u8],
) -> Result<Self, TlvLvError> { ) -> Result<Self, TlvLvError> {
@@ -641,14 +732,6 @@ impl<'first_name, 'second_name> FilestoreRequestTlv<'first_name, 'second_name> {
}) })
} }
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
// The API should ensure the data field is never too large, so unwrapping here is okay.
TlvOwned::new(TlvType::FilestoreRequest, &self.to_vec()[2..]).unwrap()
}
}
impl WritableTlv for FilestoreRequestTlv<'_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
if buf.len() < self.len_full() { if buf.len() < self.len_full() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
@@ -674,17 +757,31 @@ impl WritableTlv for FilestoreRequestTlv<'_, '_> {
Ok(current_idx) Ok(current_idx)
} }
#[inline]
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.len_full() self.len_full()
} }
} }
impl WritableTlv for FilestoreRequestTlv<'_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.write_to_bytes(buf)
}
#[inline]
fn len_written(&self) -> usize {
self.len_written()
}
}
impl GenericTlv for FilestoreRequestTlv<'_, '_> { impl GenericTlv for FilestoreRequestTlv<'_, '_> {
#[inline]
fn tlv_type_field(&self) -> TlvTypeField { fn tlv_type_field(&self) -> TlvTypeField {
TlvTypeField::Standard(TlvType::FilestoreRequest) TlvTypeField::Standard(TlvType::FilestoreRequest)
} }
} }
/// Filestore response TLV.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -716,6 +813,8 @@ impl<'first_name, 'second_name, 'fs_msg> FilestoreResponseTlv<'first_name, 'seco
Lv::new_empty(), Lv::new_empty(),
) )
} }
/// Generic constructor.
pub fn new( pub fn new(
action_code: FilestoreActionCode, action_code: FilestoreActionCode,
status_code: u8, status_code: u8,
@@ -744,6 +843,7 @@ impl<'first_name, 'second_name, 'fs_msg> FilestoreResponseTlv<'first_name, 'seco
}) })
} }
/// Check whether this response has a second filename.
pub fn has_second_filename(action_code: FilestoreActionCode) -> bool { pub fn has_second_filename(action_code: FilestoreActionCode) -> bool {
if action_code == FilestoreActionCode::RenameFile if action_code == FilestoreActionCode::RenameFile
|| action_code == FilestoreActionCode::AppendFile || action_code == FilestoreActionCode::AppendFile
@@ -754,30 +854,43 @@ impl<'first_name, 'second_name, 'fs_msg> FilestoreResponseTlv<'first_name, 'seco
false false
} }
/// Action code.
#[inline]
pub fn action_code(&self) -> FilestoreActionCode { pub fn action_code(&self) -> FilestoreActionCode {
self.base.action_code self.base.action_code
} }
/// Status code.
#[inline]
pub fn status_code(&self) -> u8 { pub fn status_code(&self) -> u8 {
self.status_code self.status_code
} }
/// First name as [Lv].
#[inline]
pub fn first_name(&self) -> Lv<'first_name> { pub fn first_name(&self) -> Lv<'first_name> {
self.base.first_name self.base.first_name
} }
/// Optional second name as [Lv].
#[inline]
pub fn second_name(&self) -> Option<Lv<'second_name>> { pub fn second_name(&self) -> Option<Lv<'second_name>> {
self.base.second_name self.base.second_name
} }
/// Length of the value field.
#[inline]
pub fn len_value(&self) -> usize { pub fn len_value(&self) -> usize {
self.base.base_len_value() + self.filestore_message.len_full() self.base.base_len_value() + self.filestore_message.len_full()
} }
/// Full length of the TLV.
#[inline]
pub fn len_full(&self) -> usize { pub fn len_full(&self) -> usize {
2 + self.len_value() 2 + self.len_value()
} }
/// Construct from a raw bytestream.
pub fn from_bytes<'buf: 'first_name + 'second_name + 'fs_msg>( pub fn from_bytes<'buf: 'first_name + 'second_name + 'fs_msg>(
buf: &'buf [u8], buf: &'buf [u8],
) -> Result<Self, TlvLvError> { ) -> Result<Self, TlvLvError> {
@@ -832,14 +945,6 @@ impl<'first_name, 'second_name, 'fs_msg> FilestoreResponseTlv<'first_name, 'seco
}) })
} }
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned {
// The API should ensure the data field is never too large, so unwrap is okay here.
TlvOwned::new(TlvType::FilestoreResponse, &self.to_vec()[2..]).unwrap()
}
}
impl WritableTlv for FilestoreResponseTlv<'_, '_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
if buf.len() < self.len_full() { if buf.len() < self.len_full() {
return Err(ByteConversionError::ToSliceTooSmall { return Err(ByteConversionError::ToSliceTooSmall {
@@ -872,19 +977,34 @@ impl WritableTlv for FilestoreResponseTlv<'_, '_, '_> {
} }
} }
impl WritableTlv for FilestoreResponseTlv<'_, '_, '_> {
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.write_to_bytes(buf)
}
#[inline]
fn len_written(&self) -> usize {
self.len_written()
}
}
impl GenericTlv for FilestoreResponseTlv<'_, '_, '_> { impl GenericTlv for FilestoreResponseTlv<'_, '_, '_> {
#[inline]
fn tlv_type_field(&self) -> TlvTypeField { fn tlv_type_field(&self) -> TlvTypeField {
TlvTypeField::Standard(TlvType::FilestoreResponse) TlvTypeField::Standard(TlvType::FilestoreResponse)
} }
} }
pub(crate) fn verify_tlv_type(raw_type: u8, expected_tlv_type: TlvType) -> Result<(), TlvLvError> { pub(crate) fn verify_tlv_type(
let tlv_type = TlvType::try_from(raw_type).map_err(|_| TlvLvError::InvalidTlvTypeField { raw_type: u8,
expected_tlv_type: TlvType,
) -> Result<(), InvalidTlvTypeFieldError> {
let tlv_type = TlvType::try_from(raw_type).map_err(|_| InvalidTlvTypeFieldError {
found: raw_type, found: raw_type,
expected: Some(expected_tlv_type.into()), expected: Some(expected_tlv_type.into()),
})?; })?;
if tlv_type != expected_tlv_type { if tlv_type != expected_tlv_type {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: tlv_type as u8, found: tlv_type as u8,
expected: Some(expected_tlv_type as u8), expected: Some(expected_tlv_type as u8),
}); });
@@ -1079,11 +1199,15 @@ mod tests {
let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large); let tlv_res = Tlv::new(TlvType::MsgToUser, &buf_too_large);
assert!(tlv_res.is_err()); assert!(tlv_res.is_err());
let error = tlv_res.unwrap_err(); let error = tlv_res.unwrap_err();
assert_eq!(error.0, u8::MAX as usize + 1); match error {
assert_eq!( TlvLvDataTooLargeError(size) => {
error.to_string(), assert_eq!(size, u8::MAX as usize + 1);
"data with size 256 larger than allowed 255 bytes" assert_eq!(
); error.to_string(),
"data with size 256 larger than allowed 255 bytes"
);
}
}
} }
#[test] #[test]
@@ -1392,7 +1516,8 @@ mod tests {
let error = EntityIdTlv::try_from(msg_to_user_tlv); let error = EntityIdTlv::try_from(msg_to_user_tlv);
assert!(error.is_err()); assert!(error.is_err());
let error = error.unwrap_err(); let error = error.unwrap_err();
if let TlvLvError::InvalidTlvTypeField { found, expected } = error { if let TlvLvError::InvalidTlvTypeField(InvalidTlvTypeFieldError { found, expected }) = error
{
assert_eq!(found, TlvType::MsgToUser as u8); assert_eq!(found, TlvType::MsgToUser as u8);
assert_eq!(expected, Some(TlvType::EntityId as u8)); assert_eq!(expected, Some(TlvType::EntityId as u8));
assert_eq!( assert_eq!(
@@ -1457,7 +1582,7 @@ mod tests {
let entity_id = UbfU8::new(5); let entity_id = UbfU8::new(5);
let mut buf: [u8; 4] = [0; 4]; let mut buf: [u8; 4] = [0; 4];
assert!(entity_id.write_to_be_bytes(&mut buf).is_ok()); assert!(entity_id.write_to_be_bytes(&mut buf).is_ok());
let tlv_res = TlvOwned::new(TlvType::EntityId, &buf[0..1]).expect("creating TLV failed"); let tlv_res = TlvOwned::new(TlvType::EntityId, &buf[0..1]);
assert_eq!( assert_eq!(
tlv_res.tlv_type_field(), tlv_res.tlv_type_field(),
TlvTypeField::Standard(TlvType::EntityId) TlvTypeField::Standard(TlvType::EntityId)
@@ -1484,7 +1609,7 @@ mod tests {
#[test] #[test]
fn test_owned_tlv_custom_type() { fn test_owned_tlv_custom_type() {
let tlv_res = TlvOwned::new_with_custom_type(32, &[]).unwrap(); let tlv_res = TlvOwned::new_with_custom_type(32, &[]);
assert_eq!(tlv_res.tlv_type_field(), TlvTypeField::Custom(32)); assert_eq!(tlv_res.tlv_type_field(), TlvTypeField::Custom(32));
assert_eq!(tlv_res.len_full(), 2); assert_eq!(tlv_res.len_full(), 2);
assert_eq!(tlv_res.value().len(), 0); assert_eq!(tlv_res.value().len(), 0);
+46 -18
View File
@@ -2,17 +2,22 @@
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use super::TlvOwned; use super::TlvOwned;
use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv}; use super::{GenericTlv, ReadableTlv, Tlv, TlvLvError, TlvType, TlvTypeField, WritableTlv};
use crate::{cfdp::TlvLvDataTooLarge, ByteConversionError}; use crate::{
cfdp::{InvalidTlvTypeFieldError, TlvLvDataTooLargeError},
ByteConversionError,
};
use delegate::delegate; use delegate::delegate;
/// Message To User TLV structure.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct MsgToUserTlv<'data> { pub struct MsgToUserTlv<'data> {
/// Wrapped generic TLV structure.
pub tlv: Tlv<'data>, pub tlv: Tlv<'data>,
} }
impl<'data> MsgToUserTlv<'data> { impl<'data> MsgToUserTlv<'data> {
/// Create a new message to user TLV where the type field is set correctly. /// Create a new message to user TLV where the type field is set correctly.
pub fn new(value: &'data [u8]) -> Result<MsgToUserTlv<'data>, TlvLvDataTooLarge> { pub fn new(value: &'data [u8]) -> Result<MsgToUserTlv<'data>, TlvLvDataTooLargeError> {
Ok(Self { Ok(Self {
tlv: Tlv::new(TlvType::MsgToUser, value)?, tlv: Tlv::new(TlvType::MsgToUser, value)?,
}) })
@@ -20,7 +25,9 @@ impl<'data> MsgToUserTlv<'data> {
delegate! { delegate! {
to self.tlv { to self.tlv {
/// Value field of the TLV.
pub fn value(&self) -> &[u8]; pub fn value(&self) -> &[u8];
/// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of /// Helper method to retrieve the length of the value. Simply calls the [slice::len] method of
/// [Self::value] /// [Self::value]
pub fn len_value(&self) -> usize; pub fn len_value(&self) -> usize;
@@ -34,12 +41,16 @@ impl<'data> MsgToUserTlv<'data> {
} }
} }
/// Is this a standard TLV?
#[inline]
pub fn is_standard_tlv(&self) -> bool { pub fn is_standard_tlv(&self) -> bool {
true true
} }
pub fn tlv_type(&self) -> Option<TlvType> { /// TLV type field.
Some(TlvType::MsgToUser) #[inline]
pub fn tlv_type(&self) -> TlvType {
TlvType::MsgToUser
} }
/// Check whether this message is a reserved CFDP message like a Proxy Operation Message. /// Check whether this message is a reserved CFDP message like a Proxy Operation Message.
@@ -62,30 +73,47 @@ impl<'data> MsgToUserTlv<'data> {
match msg_to_user.tlv.tlv_type_field() { match msg_to_user.tlv.tlv_type_field() {
TlvTypeField::Standard(tlv_type) => { TlvTypeField::Standard(tlv_type) => {
if tlv_type != TlvType::MsgToUser { if tlv_type != TlvType::MsgToUser {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: tlv_type as u8, found: tlv_type as u8,
expected: Some(TlvType::MsgToUser as u8), expected: Some(TlvType::MsgToUser as u8),
}); }
.into());
} }
} }
TlvTypeField::Custom(raw) => { TlvTypeField::Custom(raw) => {
return Err(TlvLvError::InvalidTlvTypeField { return Err(InvalidTlvTypeFieldError {
found: raw, found: raw,
expected: Some(TlvType::MsgToUser as u8), expected: Some(TlvType::MsgToUser as u8),
}); }
.into());
} }
} }
Ok(msg_to_user) Ok(msg_to_user)
} }
/// Convert to a generic [Tlv].
#[inline]
pub fn to_tlv(&self) -> Tlv<'data> { pub fn to_tlv(&self) -> Tlv<'data> {
self.tlv self.tlv
} }
/// Convert to an [TlvOwned].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn to_owned(&self) -> TlvOwned { pub fn to_owned(&self) -> TlvOwned {
self.tlv.to_owned() self.tlv.to_owned()
} }
#[inline]
fn len_written(&self) -> usize {
self.len_full()
}
delegate!(
to self.tlv {
/// Write the TLV to a byte buffer.
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
}
);
} }
impl<'a> From<MsgToUserTlv<'a>> for Tlv<'a> { impl<'a> From<MsgToUserTlv<'a>> for Tlv<'a> {
@@ -95,18 +123,18 @@ impl<'a> From<MsgToUserTlv<'a>> for Tlv<'a> {
} }
impl WritableTlv for MsgToUserTlv<'_> { impl WritableTlv for MsgToUserTlv<'_> {
#[inline]
fn len_written(&self) -> usize { fn len_written(&self) -> usize {
self.len_full() self.len_written()
} }
delegate!( fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
to self.tlv { self.tlv.write_to_bytes(buf)
fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; }
}
);
} }
impl GenericTlv for MsgToUserTlv<'_> { impl GenericTlv for MsgToUserTlv<'_> {
#[inline]
fn tlv_type_field(&self) -> TlvTypeField { fn tlv_type_field(&self) -> TlvTypeField {
self.tlv.tlv_type_field() self.tlv.tlv_type_field()
} }
@@ -123,7 +151,7 @@ mod tests {
assert!(msg_to_user.is_ok()); assert!(msg_to_user.is_ok());
let msg_to_user = msg_to_user.unwrap(); let msg_to_user = msg_to_user.unwrap();
assert!(msg_to_user.is_standard_tlv()); assert!(msg_to_user.is_standard_tlv());
assert_eq!(msg_to_user.tlv_type().unwrap(), TlvType::MsgToUser); assert_eq!(msg_to_user.tlv_type(), TlvType::MsgToUser);
assert_eq!( assert_eq!(
msg_to_user.tlv_type_field(), msg_to_user.tlv_type_field(),
TlvTypeField::Standard(TlvType::MsgToUser) TlvTypeField::Standard(TlvType::MsgToUser)
@@ -205,9 +233,9 @@ mod tests {
fn test_reserved_msg_deserialization_invalid_type() { fn test_reserved_msg_deserialization_invalid_type() {
let trash: [u8; 5] = [TlvType::FlowLabel as u8, 3, 1, 2, 3]; let trash: [u8; 5] = [TlvType::FlowLabel as u8, 3, 1, 2, 3];
let error = MsgToUserTlv::from_bytes(&trash).unwrap_err(); let error = MsgToUserTlv::from_bytes(&trash).unwrap_err();
if let TlvLvError::InvalidTlvTypeField { found, expected } = error { if let TlvLvError::InvalidTlvTypeField(inner) = error {
assert_eq!(found, TlvType::FlowLabel as u8); assert_eq!(inner.found, TlvType::FlowLabel as u8);
assert_eq!(expected, Some(TlvType::MsgToUser as u8)); assert_eq!(inner.expected, Some(TlvType::MsgToUser as u8));
} else { } else {
panic!("Wrong error type returned: {:?}", error); panic!("Wrong error type returned: {:?}", error);
} }
+15
View File
@@ -0,0 +1,15 @@
//! # CRC checksum support.
//!
//! Thin wrapper around the [crc] crate.
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// a [crc::NoTable] as the CRC implementation.
pub const CRC_CCITT_FALSE_NO_TABLE: crc::Crc<u16, crc::NoTable> =
crc::Crc::<u16, crc::NoTable>::new(&crc::CRC_16_IBM_3740);
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// [crc::Table<1>] as the CRC implementation.
pub const CRC_CCITT_FALSE: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_3740);
/// CRC algorithm used by the PUS standard, the CCSDS TC standard and the CFDP standard, using
/// a [crc::Table<16>] large table as the CRC implementation.
pub const CRC_CCITT_FALSE_BIG_TABLE: crc::Crc<u16, crc::Table<16>> =
crc::Crc::<u16, crc::Table<16>>::new(&crc::CRC_16_IBM_3740);
+14 -5
View File
@@ -3,17 +3,26 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Event service subtype ID.
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum MessageSubtypeId {
/// Telemetry - Info report.
TmInfoReport = 1, TmInfoReport = 1,
/// Telemetry - Low severity report.
TmLowSeverityReport = 2, TmLowSeverityReport = 2,
/// Telemetry - Medium severity report.
TmMediumSeverityReport = 3, TmMediumSeverityReport = 3,
/// Telemetry - High severity report.
TmHighSeverityReport = 4, TmHighSeverityReport = 4,
/// Telecommand - Enable event generation.
TcEnableEventGeneration = 5, TcEnableEventGeneration = 5,
/// Telecommand - Disable event generation.
TcDisableEventGeneration = 6, TcDisableEventGeneration = 6,
/// Telecommand - Report disabled list.
TcReportDisabledList = 7, TcReportDisabledList = 7,
/// Telemetry - Disabled events report.
TmDisabledEventsReport = 8, TmDisabledEventsReport = 8,
} }
@@ -23,19 +32,19 @@ mod tests {
#[test] #[test]
fn test_conv_into_u8() { fn test_conv_into_u8() {
let subservice: u8 = Subservice::TmLowSeverityReport.into(); let subservice: u8 = MessageSubtypeId::TmLowSeverityReport.into();
assert_eq!(subservice, 2); assert_eq!(subservice, 2);
} }
#[test] #[test]
fn test_conv_from_u8() { fn test_conv_from_u8() {
let subservice: Subservice = 2.try_into().unwrap(); let subservice: MessageSubtypeId = 2.try_into().unwrap();
assert_eq!(subservice, Subservice::TmLowSeverityReport); assert_eq!(subservice, MessageSubtypeId::TmLowSeverityReport);
} }
#[test] #[test]
fn test_conv_fails() { fn test_conv_fails() {
let conversion = Subservice::try_from(9); let conversion = MessageSubtypeId::try_from(9);
assert!(conversion.is_err()); assert!(conversion.is_err());
let err = conversion.unwrap_err(); let err = conversion.unwrap_err();
assert_eq!(err.number, 9); assert_eq!(err.number, 9);
+28 -9
View File
@@ -3,31 +3,49 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Housekeeping service subtype ID.
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum MessageSubtypeId {
// Regular HK // Regular HK
/// Telecommand - Create Housekeeping Report Structure.
TcCreateHkReportStructure = 1, TcCreateHkReportStructure = 1,
/// Telecommand - Delete HK report structures.
TcDeleteHkReportStructures = 3, TcDeleteHkReportStructures = 3,
/// Telecommand - Enable HK generation.
TcEnableHkGeneration = 5, TcEnableHkGeneration = 5,
/// Telecommand - Disable HK generation.
TcDisableHkGeneration = 6, TcDisableHkGeneration = 6,
/// Telecommand - Report HK report structures.
TcReportHkReportStructures = 9, TcReportHkReportStructures = 9,
/// Telemetry - HK report.
TmHkPacket = 25, TmHkPacket = 25,
/// Telecommand - Generate one-shot report.
TcGenerateOneShotHk = 27, TcGenerateOneShotHk = 27,
/// Telecommand - Modify collection interval.
TcModifyHkCollectionInterval = 31, TcModifyHkCollectionInterval = 31,
// Diagnostics HK /// Telecommand - Create diagnostics report structures.
TcCreateDiagReportStructure = 2, TcCreateDiagReportStructure = 2,
/// Telecommand - Delete diagnostics report structures.
TcDeleteDiagReportStructures = 4, TcDeleteDiagReportStructures = 4,
/// Telecommand - Enable diagnostics generation.
TcEnableDiagGeneration = 7, TcEnableDiagGeneration = 7,
/// Telecommand - Disable diagnostics generation.
TcDisableDiagGeneration = 8, TcDisableDiagGeneration = 8,
/// Telemetry - HK structures report.
TmHkStructuresReport = 10, TmHkStructuresReport = 10,
/// Telecommand - Report diagnostics report structures.
TcReportDiagReportStructures = 11, TcReportDiagReportStructures = 11,
/// Telemetry - Diagnostics report structures.
TmDiagStructuresReport = 12, TmDiagStructuresReport = 12,
/// Telemetry - Diagnostics packet.
TmDiagPacket = 26, TmDiagPacket = 26,
/// Telecommand - Generate one-shot diagnostics report.
TcGenerateOneShotDiag = 28, TcGenerateOneShotDiag = 28,
/// Telecommand - Modify diagnostics interval report.
TcModifyDiagCollectionInterval = 32, TcModifyDiagCollectionInterval = 32,
} }
@@ -37,25 +55,26 @@ mod tests {
#[test] #[test]
fn test_try_from_u8() { fn test_try_from_u8() {
let hk_report_subservice_raw = 25; let hk_report_subservice_raw = 25;
let hk_report: Subservice = Subservice::try_from(hk_report_subservice_raw).unwrap(); let hk_report: MessageSubtypeId =
assert_eq!(hk_report, Subservice::TmHkPacket); MessageSubtypeId::try_from(hk_report_subservice_raw).unwrap();
assert_eq!(hk_report, MessageSubtypeId::TmHkPacket);
} }
#[test] #[test]
fn test_into_u8() { fn test_into_u8() {
let hk_report_raw: u8 = Subservice::TmHkPacket.into(); let hk_report_raw: u8 = MessageSubtypeId::TmHkPacket.into();
assert_eq!(hk_report_raw, 25); assert_eq!(hk_report_raw, 25);
} }
#[test] #[test]
fn test_partial_eq() { fn test_partial_eq() {
let hk_report_raw = Subservice::TmHkPacket; let hk_report_raw = MessageSubtypeId::TmHkPacket;
assert_ne!(hk_report_raw, Subservice::TcGenerateOneShotHk); assert_ne!(hk_report_raw, MessageSubtypeId::TcGenerateOneShotHk);
assert_eq!(hk_report_raw, Subservice::TmHkPacket); assert_eq!(hk_report_raw, MessageSubtypeId::TmHkPacket);
} }
#[test] #[test]
fn test_copy_clone() { fn test_copy_clone() {
let hk_report = Subservice::TmHkPacket; let hk_report = MessageSubtypeId::TmHkPacket;
let hk_report_copy = hk_report; let hk_report_copy = hk_report;
assert_eq!(hk_report, hk_report_copy); assert_eq!(hk_report, hk_report_copy);
} }
+249 -118
View File
@@ -3,94 +3,105 @@
//! //!
//! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry //! You can find the PUS telecommand types in the [tc] module and the the PUS telemetry
//! types inside the [tm] module. //! types inside the [tm] module.
use crate::{ByteConversionError, CcsdsPacket, CRC_CCITT_FALSE}; use crate::{
crc::{CRC_CCITT_FALSE, CRC_CCITT_FALSE_NO_TABLE},
ByteConversionError, CcsdsPacket,
};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::{Debug, Display, Formatter}; use arbitrary_int::u4;
use core::fmt::Debug;
use core::mem::size_of; use core::mem::size_of;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub mod event; pub mod event;
pub mod hk; pub mod hk;
pub mod scheduling; pub mod scheduling;
pub mod tc; pub mod tc;
pub mod tc_pus_a;
pub mod tm; pub mod tm;
pub mod tm_pus_a;
pub mod verification; pub mod verification;
/// Type alias for the CRC16 type.
pub type CrcType = u16; pub type CrcType = u16;
/// Standard PUS service IDs.
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
#[non_exhaustive] #[non_exhaustive]
pub enum PusServiceId { pub enum PusServiceId {
/// Service 1 /// Service 1 Verification
Verification = 1, Verification = 1,
/// Service 2 /// Service 2 Device Access
DeviceAccess = 2, DeviceAccess = 2,
/// Service 3 /// Service 3 Housekeeping
Housekeeping = 3, Housekeeping = 3,
/// Service 4 /// Service 4 Parameter Statistics
ParameterStatistics = 4, ParameterStatistics = 4,
/// Service 5 /// Service 5 Event
Event = 5, Event = 5,
/// Service 6 /// Service 6 Memory Management
MemoryManagement = 6, MemoryManagement = 6,
/// Service 8 /// Service 8 Action
Action = 8, Action = 8,
/// Service 9 /// Service 9 Time Management
TimeManagement = 9, TimeManagement = 9,
/// Service 11 /// Service 11 Scheduling
Scheduling = 11, Scheduling = 11,
/// Service 12 /// Service 12 On-Board Monitoring
OnBoardMonitoring = 12, OnBoardMonitoring = 12,
/// Service 13 /// Service 13 Large Packet Transfer
LargePacketTransfer = 13, LargePacketTransfer = 13,
/// Service 14 /// Service 14 Real-Time Forwarding Control
RealTimeForwardingControl = 14, RealTimeForwardingControl = 14,
/// Service 15 /// Service 15 Storage And Retrival
StorageAndRetrival = 15, StorageAndRetrival = 15,
/// Service 17 /// Service 17 Test
Test = 17, Test = 17,
/// Service 18 /// Service 18 Operations And Procedures
OpsAndProcedures = 18, OpsAndProcedures = 18,
/// Service 19 /// Service 19 Event Action
EventAction = 19, EventAction = 19,
/// Service 20 /// Service 20 Parameter
Parameter = 20, Parameter = 20,
/// Service 21 /// Service 21 Request Sequencing
RequestSequencing = 21, RequestSequencing = 21,
/// Service 22 /// Service 22 Position Based Scheduling
PositionBasedScheduling = 22, PositionBasedScheduling = 22,
/// Service 23 /// Service 23 File Management
FileManagement = 23, FileManagement = 23,
} }
/// All PUS versions. Only PUS C is supported by this library. /// All PUS versions. Only PUS C is supported by this library.
#[derive(PartialEq, Eq, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Debug, num_enum::TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u4, exhaustive = false)]
#[repr(u8)]
#[non_exhaustive] #[non_exhaustive]
pub enum PusVersion { pub enum PusVersion {
/// ESA PUS
EsaPus = 0, EsaPus = 0,
/// PUS A
PusA = 1, PusA = 1,
/// PUS C
PusC = 2, PusC = 2,
Invalid = 0b1111,
} }
impl TryFrom<u8> for PusVersion { impl TryFrom<u4> for PusVersion {
type Error = (); type Error = u4;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u4) -> Result<Self, Self::Error> {
match value { match value {
x if x == PusVersion::EsaPus as u8 => Ok(PusVersion::EsaPus), x if x == PusVersion::EsaPus.raw_value() => Ok(PusVersion::EsaPus),
x if x == PusVersion::PusA as u8 => Ok(PusVersion::PusA), x if x == PusVersion::PusA.raw_value() => Ok(PusVersion::PusA),
x if x == PusVersion::PusC as u8 => Ok(PusVersion::PusC), x if x == PusVersion::PusC.raw_value() => Ok(PusVersion::PusC),
_ => Err(()), _ => Err(value),
} }
} }
} }
@@ -98,44 +109,70 @@ impl TryFrom<u8> for PusVersion {
/// ECSS Packet Type Codes (PTC)s. /// ECSS Packet Type Codes (PTC)s.
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum PacketTypeCodes { pub enum PacketTypeCodes {
/// Boolean.
Boolean = 1, Boolean = 1,
/// Enumerated.
Enumerated = 2, Enumerated = 2,
/// Unsigned Integer.
UnsignedInt = 3, UnsignedInt = 3,
/// Signed Integer.
SignedInt = 4, SignedInt = 4,
/// Real (floating point).
Real = 5, Real = 5,
/// Bit string.
BitString = 6, BitString = 6,
/// Octet (byte) string.
OctetString = 7, OctetString = 7,
/// Character string.
CharString = 8, CharString = 8,
/// Absolute time.
AbsoluteTime = 9, AbsoluteTime = 9,
/// Relative time.
RelativeTime = 10, RelativeTime = 10,
/// Deduced.
Deduced = 11, Deduced = 11,
/// Packet.
Packet = 12, Packet = 12,
} }
/// Type alias for the ECSS Packet Type Codes (PTC)s.
pub type Ptc = PacketTypeCodes; pub type Ptc = PacketTypeCodes;
/// ECSS Packet Field Codes (PFC)s for the unsigned [Ptc]. /// ECSS Packet Field Codes (PFC)s for the unsigned [Ptc].
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum PfcUnsigned { pub enum PfcUnsigned {
/// 1 byte.
OneByte = 4, OneByte = 4,
/// 12 bits.
TwelveBits = 8, TwelveBits = 8,
/// 2 bytes.
TwoBytes = 12, TwoBytes = 12,
/// 3 bytes.
ThreeBytes = 13, ThreeBytes = 13,
/// 4 bytes.
FourBytes = 14, FourBytes = 14,
/// 6 bytes.
SixBytes = 15, SixBytes = 15,
/// 8 bytes.
EightBytes = 16, EightBytes = 16,
/// 1 bit.
OneBit = 17, OneBit = 17,
/// 2 bits.
TwoBits = 18, TwoBits = 18,
/// 3 bits.
ThreeBits = 19, ThreeBits = 19,
} }
/// ECSS Packet Field Codes (PFC)s for the real (floating point) [Ptc]. /// ECSS Packet Field Codes (PFC)s for the real (floating point) [Ptc].
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum PfcReal { pub enum PfcReal {
/// 4 octets simple precision format (IEEE) /// 4 octets simple precision format (IEEE)
@@ -148,62 +185,76 @@ pub enum PfcReal {
DoubleMilStd = 4, DoubleMilStd = 4,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Generic PUS error.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PusError { pub enum PusError {
VersionNotSupported(PusVersion), /// PUS version is not supported.
#[error("PUS version {0:?} not supported")]
VersionNotSupported(u4),
/// Checksum failure.
#[error("checksum verification for crc16 {0:#06x} failed")]
ChecksumFailure(u16), ChecksumFailure(u16),
/// CRC16 needs to be calculated first /// CRC16 needs to be calculated first
CrcCalculationMissing, //#[error("crc16 was not calculated")]
ByteConversion(ByteConversionError), //CrcCalculationMissing,
#[error("pus error: {0}")]
ByteConversion(#[from] ByteConversionError),
} }
impl Display for PusError { /// Message type ID field.
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
match self { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
PusError::VersionNotSupported(v) => { #[cfg_attr(feature = "defmt", derive(defmt::Format))]
write!(f, "PUS version {v:?} not supported") pub struct MessageTypeId {
} /// Service type ID.
PusError::ChecksumFailure(crc) => { pub type_id: u8,
write!(f, "checksum verification for crc16 {crc:#06x} failed") /// Subtype ID.
} pub subtype_id: u8,
PusError::CrcCalculationMissing => { }
write!(f, "crc16 was not calculated")
} impl MessageTypeId {
PusError::ByteConversion(e) => { /// Generic constructor.
write!(f, "pus error: {e}") pub const fn new(type_id: u8, subtype_id: u8) -> Self {
} Self {
type_id,
subtype_id,
} }
} }
} }
#[cfg(feature = "std")]
impl Error for PusError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let PusError::ByteConversion(e) = self {
return Some(e);
}
None
}
}
impl From<ByteConversionError> for PusError {
fn from(e: ByteConversionError) -> Self {
PusError::ByteConversion(e)
}
}
/// Generic trait to describe common attributes for both PUS Telecommands (TC) and PUS Telemetry /// 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. /// (TM) packets. All PUS packets are also a special type of [CcsdsPacket]s.
pub trait PusPacket: CcsdsPacket { pub trait PusPacket: CcsdsPacket {
const PUS_VERSION: PusVersion = PusVersion::PusC; /// PUS version.
fn pus_version(&self) -> Result<PusVersion, u4>;
fn pus_version(&self) -> PusVersion; /// Message type ID.
fn service(&self) -> u8; fn message_type_id(&self) -> MessageTypeId;
fn subservice(&self) -> u8;
/// Service type ID.
#[inline]
fn service_type_id(&self) -> u8 {
self.message_type_id().type_id
}
/// Message subtype ID.
#[inline]
fn message_subtype_id(&self) -> u8 {
self.message_type_id().subtype_id
}
/// User data field.
fn user_data(&self) -> &[u8]; fn user_data(&self) -> &[u8];
fn crc16(&self) -> Option<u16>;
/// CRC-16-CCITT checksum.
fn checksum(&self) -> Option<u16>;
/// The presence of the CRC-16-CCITT checksum is optional.
fn has_checksum(&self) -> bool {
self.checksum().is_some()
}
} }
pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> { pub(crate) fn crc_from_raw_data(raw_data: &[u8]) -> Result<u16, ByteConversionError> {
@@ -230,17 +281,23 @@ pub(crate) fn user_data_from_raw(
current_idx: usize, current_idx: usize,
total_len: usize, total_len: usize,
slice: &[u8], slice: &[u8],
has_checksum: bool,
) -> Result<&[u8], ByteConversionError> { ) -> Result<&[u8], ByteConversionError> {
match current_idx { if has_checksum {
_ if current_idx > total_len - 2 => Err(ByteConversionError::FromSliceTooSmall { if current_idx > total_len - 2 {
found: total_len - 2, return Err(ByteConversionError::FromSliceTooSmall {
expected: current_idx, found: total_len - 2,
}), expected: current_idx,
_ => Ok(&slice[current_idx..total_len - 2]), });
}
Ok(&slice[current_idx..total_len - 2])
} else {
Ok(&slice[current_idx..total_len])
} }
} }
pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error( /// Verify the CRC16 of a raw packet and return a [PusError] on failure.
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error(
raw_data: &[u8], raw_data: &[u8],
crc16: u16, crc16: u16,
) -> Result<(), PusError> { ) -> Result<(), PusError> {
@@ -249,7 +306,19 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw_to_pus_error(
.ok_or(PusError::ChecksumFailure(crc16)) .ok_or(PusError::ChecksumFailure(crc16))
} }
pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool { /// Verify the CRC16 of a raw packet using a table-less implementation and return a [PusError] on
/// failure.
pub fn verify_crc16_ccitt_false_from_raw_to_pus_error_no_table(
raw_data: &[u8],
crc16: u16,
) -> Result<(), PusError> {
verify_crc16_ccitt_false_from_raw_no_table(raw_data)
.then_some(())
.ok_or(PusError::ChecksumFailure(crc16))
}
/// Verify the CRC16 of a raw packet.
pub fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool {
let mut digest = CRC_CCITT_FALSE.digest(); let mut digest = CRC_CCITT_FALSE.digest();
digest.update(raw_data); digest.update(raw_data);
if digest.finalize() == 0 { if digest.finalize() == 0 {
@@ -258,28 +327,28 @@ pub(crate) fn verify_crc16_ccitt_false_from_raw(raw_data: &[u8]) -> bool {
false false
} }
macro_rules! ccsds_impl { /// Verify the CRC16 of a raw packet, using the table-less implementation.
() => { pub fn verify_crc16_ccitt_false_from_raw_no_table(raw_data: &[u8]) -> bool {
delegate!(to self.sp_header { let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
#[inline] digest.update(raw_data);
fn ccsds_version(&self) -> u8; if digest.finalize() == 0 {
#[inline] return true;
fn packet_id(&self) -> crate::PacketId;
#[inline]
fn psc(&self) -> crate::PacketSequenceCtrl;
#[inline]
fn data_len(&self) -> u16;
});
} }
false
} }
macro_rules! sp_header_impls { macro_rules! sp_header_impls {
() => { () => {
delegate!(to self.sp_header { delegate!(to self.sp_header {
/// Set the CCSDS APID.
#[inline] #[inline]
pub fn set_apid(&mut self, apid: u16) -> bool; pub fn set_apid(&mut self, apid: u11);
/// Set the CCSDS sequence count.
#[inline] #[inline]
pub fn set_seq_count(&mut self, seq_count: u16) -> bool; pub fn set_seq_count(&mut self, seq_count: u14);
/// Set the CCSDS sequence flags.
#[inline] #[inline]
pub fn set_seq_flags(&mut self, seq_flag: SequenceFlags); pub fn set_seq_flags(&mut self, seq_flag: SequenceFlags);
}); });
@@ -287,7 +356,6 @@ macro_rules! sp_header_impls {
} }
use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum}; use crate::util::{GenericUnsignedByteField, ToBeBytes, UnsignedEnum};
pub(crate) use ccsds_impl;
pub(crate) use sp_header_impls; pub(crate) use sp_header_impls;
/// Generic trait for ECSS enumeration which consist of a PFC field denoting their bit length /// Generic trait for ECSS enumeration which consist of a PFC field denoting their bit length
@@ -299,27 +367,28 @@ pub trait EcssEnumeration: UnsignedEnum {
fn pfc(&self) -> u8; fn pfc(&self) -> u8;
} }
/// Extension trait for [EcssEnumeration] which adds common trait bounds.
pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {} pub trait EcssEnumerationExt: EcssEnumeration + Debug + Copy + Clone + PartialEq + Eq {}
/// ECSS enumerated type wrapper.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GenericEcssEnumWrapper<TYPE: Copy + Into<u64>> { pub struct GenericEcssEnumWrapper<TYPE: Copy + Into<u64>>(GenericUnsignedByteField<TYPE>);
field: GenericUnsignedByteField<TYPE>,
}
impl<TYPE: Copy + Into<u64>> GenericEcssEnumWrapper<TYPE> { impl<TYPE: Copy + Into<u64>> GenericEcssEnumWrapper<TYPE> {
/// Returns [PacketTypeCodes::Enumerated].
pub const fn ptc() -> PacketTypeCodes { pub const fn ptc() -> PacketTypeCodes {
PacketTypeCodes::Enumerated PacketTypeCodes::Enumerated
} }
pub const fn value_typed(&self) -> TYPE { /// Value.
self.field.value_typed() pub const fn value(&self) -> TYPE {
self.0.value()
} }
pub fn new(val: TYPE) -> Self { /// Generic constructor.
Self { pub const fn new(val: TYPE) -> Self {
field: GenericUnsignedByteField::new(val), Self(GenericUnsignedByteField::new(val))
}
} }
} }
@@ -329,11 +398,11 @@ impl<TYPE: Copy + ToBeBytes + Into<u64>> UnsignedEnum for GenericEcssEnumWrapper
} }
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
self.field.write_to_be_bytes(buf) self.0.write_to_be_bytes(buf)
} }
fn value(&self) -> u64 { fn value_raw(&self) -> u64 {
self.field.value() self.0.value().into()
} }
} }
@@ -357,11 +426,12 @@ impl<T: Copy + Into<u64>> From<T> for GenericEcssEnumWrapper<T> {
macro_rules! generic_ecss_enum_typedefs_and_from_impls { macro_rules! generic_ecss_enum_typedefs_and_from_impls {
($($ty:ty => $Enum:ident),*) => { ($($ty:ty => $Enum:ident),*) => {
$( $(
/// Type alias for ECSS enumeration wrapper around `$ty`
pub type $Enum = GenericEcssEnumWrapper<$ty>; pub type $Enum = GenericEcssEnumWrapper<$ty>;
impl From<$Enum> for $ty { impl From<$Enum> for $ty {
fn from(value: $Enum) -> Self { fn from(value: $Enum) -> Self {
value.value_typed() value.value()
} }
} }
)* )*
@@ -381,8 +451,48 @@ generic_ecss_enum_typedefs_and_from_impls! {
/// byte representation. This is especially useful for generic abstractions which depend only /// byte representation. This is especially useful for generic abstractions which depend only
/// on the serialization of those packets. /// on the serialization of those packets.
pub trait WritablePusPacket { pub trait WritablePusPacket {
/// The length here also includes the CRC length.
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError>;
/// Checksum generation is enabled for the packet.
fn has_checksum(&self) -> bool;
/// Writes the packet to the given slice without writing the CRC checksum.
///
/// The returned size is the written size WITHOUT the CRC checksum.
/// If the checksum generation is disabled, this function is identical to the APIs which
/// generate a checksum.
fn write_to_bytes_no_checksum(&self, slice: &mut [u8]) -> Result<usize, PusError>;
/// First uses [Self::write_to_bytes_no_checksum] to write the packet to the given slice and
/// then uses the [CRC_CCITT_FALSE] to calculate the CRC and write it to the slice if the
/// packet is configured to include a checksum.
fn write_to_bytes(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_checksum(slice)?;
if self.has_checksum() {
let mut digest = CRC_CCITT_FALSE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
}
Ok(curr_idx)
}
/// First uses [Self::write_to_bytes_no_checksum] to write the packet to the given slice and then
/// uses the [CRC_CCITT_FALSE_NO_TABLE] to calculate the CRC and write it to the slice if
/// the paket is configured to include a checksum.
fn write_to_bytes_checksum_no_table(&self, slice: &mut [u8]) -> Result<usize, PusError> {
let mut curr_idx = self.write_to_bytes_no_checksum(slice)?;
if self.has_checksum() {
let mut digest = CRC_CCITT_FALSE_NO_TABLE.digest();
digest.update(&slice[0..curr_idx]);
slice[curr_idx..curr_idx + 2].copy_from_slice(&digest.finalize().to_be_bytes());
curr_idx += 2;
}
Ok(curr_idx)
}
/// Converts the packet into an owned [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<Vec<u8>, PusError> { fn to_vec(&self) -> Result<Vec<u8>, PusError> {
// This is the correct way to do this. See // This is the correct way to do this. See
@@ -394,6 +504,26 @@ pub trait WritablePusPacket {
} }
} }
/// PUS packet creator configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CreatorConfig {
/// Set the CCSDS data length field on construction.
pub set_ccsds_len: bool,
/// CRC-16-CCITT Checksum is present.
pub has_checksum: bool,
}
impl Default for CreatorConfig {
fn default() -> Self {
Self {
set_ccsds_len: true,
has_checksum: true,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::string::ToString; use alloc::string::ToString;
@@ -417,7 +547,7 @@ mod tests {
.expect("To byte conversion of u8 failed"); .expect("To byte conversion of u8 failed");
assert_eq!(buf[1], 1); assert_eq!(buf[1], 1);
assert_eq!(my_enum.value(), 1); assert_eq!(my_enum.value(), 1);
assert_eq!(my_enum.value_typed(), 1); assert_eq!(my_enum.value(), 1);
let enum_as_u8: u8 = my_enum.into(); let enum_as_u8: u8 = my_enum.into();
assert_eq!(enum_as_u8, 1); assert_eq!(enum_as_u8, 1);
let vec = my_enum.to_vec(); let vec = my_enum.to_vec();
@@ -436,7 +566,7 @@ mod tests {
assert_eq!(buf[1], 0x1f); assert_eq!(buf[1], 0x1f);
assert_eq!(buf[2], 0x2f); assert_eq!(buf[2], 0x2f);
assert_eq!(my_enum.value(), 0x1f2f); assert_eq!(my_enum.value(), 0x1f2f);
assert_eq!(my_enum.value_typed(), 0x1f2f); assert_eq!(my_enum.value(), 0x1f2f);
let enum_as_raw: u16 = my_enum.into(); let enum_as_raw: u16 = my_enum.into();
assert_eq!(enum_as_raw, 0x1f2f); assert_eq!(enum_as_raw, 0x1f2f);
let vec = my_enum.to_vec(); let vec = my_enum.to_vec();
@@ -473,7 +603,7 @@ mod tests {
assert_eq!(buf[3], 0x3f); assert_eq!(buf[3], 0x3f);
assert_eq!(buf[4], 0x4f); assert_eq!(buf[4], 0x4f);
assert_eq!(my_enum.value(), 0x1f2f3f4f); assert_eq!(my_enum.value(), 0x1f2f3f4f);
assert_eq!(my_enum.value_typed(), 0x1f2f3f4f); assert_eq!(my_enum.value(), 0x1f2f3f4f);
let enum_as_raw: u32 = my_enum.into(); let enum_as_raw: u32 = my_enum.into();
assert_eq!(enum_as_raw, 0x1f2f3f4f); assert_eq!(enum_as_raw, 0x1f2f3f4f);
let vec = my_enum.to_vec(); let vec = my_enum.to_vec();
@@ -511,7 +641,7 @@ mod tests {
assert_eq!(buf[6], 0x4f); assert_eq!(buf[6], 0x4f);
assert_eq!(buf[7], 0x5f); assert_eq!(buf[7], 0x5f);
assert_eq!(my_enum.value(), 0x1f2f3f4f5f); assert_eq!(my_enum.value(), 0x1f2f3f4f5f);
assert_eq!(my_enum.value_typed(), 0x1f2f3f4f5f); assert_eq!(my_enum.value(), 0x1f2f3f4f5f);
let enum_as_raw: u64 = my_enum.into(); let enum_as_raw: u64 = my_enum.into();
assert_eq!(enum_as_raw, 0x1f2f3f4f5f); assert_eq!(enum_as_raw, 0x1f2f3f4f5f);
assert_eq!(u64::from_be_bytes(buf), 0x1f2f3f4f5f); assert_eq!(u64::from_be_bytes(buf), 0x1f2f3f4f5f);
@@ -521,9 +651,10 @@ mod tests {
#[test] #[test]
fn test_pus_error_display() { fn test_pus_error_display() {
let unsupport_version = PusError::VersionNotSupported(super::PusVersion::EsaPus); let unsupport_version =
PusError::VersionNotSupported(super::PusVersion::EsaPus.raw_value());
let write_str = unsupport_version.to_string(); let write_str = unsupport_version.to_string();
assert_eq!(write_str, "PUS version EsaPus not supported") assert_eq!(write_str, "PUS version 0 not supported")
} }
#[test] #[test]
@@ -557,8 +688,8 @@ mod tests {
#[test] #[test]
fn test_pus_error_eq_impl() { fn test_pus_error_eq_impl() {
assert_eq!( assert_eq!(
PusError::VersionNotSupported(PusVersion::EsaPus), PusError::VersionNotSupported(PusVersion::EsaPus.raw_value()),
PusError::VersionNotSupported(PusVersion::EsaPus) PusError::VersionNotSupported(PusVersion::EsaPus.raw_value())
); );
} }
+42 -5
View File
@@ -3,53 +3,85 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Scheduling service subtype ID.
#[derive(Debug, PartialEq, Eq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, PartialEq, Eq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum MessageSubtypeId {
// Core subservices // Core subservices
/// Telecommand - Enable scheduling.
TcEnableScheduling = 1, TcEnableScheduling = 1,
/// Telecommand - Disable scheduling.
TcDisableScheduling = 2, TcDisableScheduling = 2,
/// Telecommand - Reset scheduling.
TcResetScheduling = 3, TcResetScheduling = 3,
/// Telecommand - Insert activity.
TcInsertActivity = 4, TcInsertActivity = 4,
/// Telecommand - Delete activity by request ID.
TcDeleteActivityByRequestId = 5, TcDeleteActivityByRequestId = 5,
/// Telecommand - Delete activity by filter.
TcDeleteActivitiesByFilter = 6, TcDeleteActivitiesByFilter = 6,
// Time shift subservices // Time shift subservices
/// Telecommand - Time shift activity by request ID.
TcTimeShiftActivityWithRequestId = 7, TcTimeShiftActivityWithRequestId = 7,
/// Telecommand - Time shift activity by filter.
TcTimeShiftActivitiesByFilter = 8, TcTimeShiftActivitiesByFilter = 8,
/// Telecommand - Time shift all.
TcTimeShiftAll = 15, TcTimeShiftAll = 15,
// Reporting subservices // Reporting subservices
/// Telecommand - Detail report by request ID.
TcDetailReportByRequestId = 9, TcDetailReportByRequestId = 9,
/// Telemetry - Detail report.
TmDetailReport = 10, TmDetailReport = 10,
/// Telecommand - Detail report by filter.
TcDetailReportByFilter = 11, TcDetailReportByFilter = 11,
/// Telecommand - Summary report by request ID.
TcSummaryReportByRequestId = 12, TcSummaryReportByRequestId = 12,
/// Telemetry - Summary report.
TmSummaryReport = 13, TmSummaryReport = 13,
/// Telecommand - Summary report by filter.
TcSummaryReportByFilter = 14, TcSummaryReportByFilter = 14,
/// Telecommand - Detail report all.
TcDetailReportAll = 16, TcDetailReportAll = 16,
/// Telecommand - Summary report all.
TcSummaryReportAll = 17, TcSummaryReportAll = 17,
// Subschedule subservices // Subschedule subservices
/// Telecommand - Report subschedule status.
TcReportSubscheduleStatus = 18, TcReportSubscheduleStatus = 18,
/// Telemetry - Subschedule status report.
TmReportSubscheduleStatus = 19, TmReportSubscheduleStatus = 19,
/// Telecommand - Enable subschedule.
TcEnableSubschedule = 20, TcEnableSubschedule = 20,
/// Telecommand - Disable subschedule.
TcDisableSubschedule = 21, TcDisableSubschedule = 21,
// Group subservices // Group subservices
/// Telecommand - Create schedule group.
TcCreateScheduleGroup = 22, TcCreateScheduleGroup = 22,
/// Telecommand - Delete schedule group.
TcDeleteScheduleGroup = 23, TcDeleteScheduleGroup = 23,
/// Telecommand - Enable schedule group.
TcEnableScheduleGroup = 24, TcEnableScheduleGroup = 24,
/// Telecommand - Disable schedule group.
TcDisableScheduleGroup = 25, TcDisableScheduleGroup = 25,
/// Telecommand - Report all group status.
TcReportAllGroupsStatus = 26, TcReportAllGroupsStatus = 26,
/// Telemetry - All group status report.
TmReportAllGroupsStatus = 27, TmReportAllGroupsStatus = 27,
} }
/// This status applies to sub-schedules and groups as well as specified in ECSS-E-ST-70-41C 8.11.3 /// This status applies to sub-schedules and groups as well as specified in ECSS-E-ST-70-41C 8.11.3
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SchedStatus { pub enum SchedStatus {
/// Scheduling disabled.
Disabled = 0, Disabled = 0,
/// Scheduling enabled.
Enabled = 1, Enabled = 1,
} }
@@ -66,11 +98,16 @@ impl From<bool> for SchedStatus {
/// Time window types as specified in ECSS-E-ST-70-41C 8.11.3 /// Time window types as specified in ECSS-E-ST-70-41C 8.11.3
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimeWindowType { pub enum TimeWindowType {
/// Select all.
SelectAll = 0, SelectAll = 0,
/// From time tag to time tag.
TimeTagToTimeTag = 1, TimeTagToTimeTag = 1,
/// Starting from a time tag.
FromTimeTag = 2, FromTimeTag = 2,
/// Until a time tag.
ToTimeTag = 3, ToTimeTag = 3,
} }
@@ -96,20 +133,20 @@ mod tests {
#[test] #[test]
fn test_conv_into_u8() { fn test_conv_into_u8() {
let subservice: u8 = Subservice::TcCreateScheduleGroup.into(); let subservice: u8 = MessageSubtypeId::TcCreateScheduleGroup.into();
assert_eq!(subservice, 22); assert_eq!(subservice, 22);
} }
#[test] #[test]
fn test_conv_from_u8() { fn test_conv_from_u8() {
let subservice: Subservice = 22u8.try_into().unwrap(); let subservice: MessageSubtypeId = 22u8.try_into().unwrap();
assert_eq!(subservice, Subservice::TcCreateScheduleGroup); assert_eq!(subservice, MessageSubtypeId::TcCreateScheduleGroup);
} }
#[test] #[test]
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_serde_subservice_id() { fn test_serde_subservice_id() {
generic_serde_test(Subservice::TcEnableScheduling); generic_serde_test(MessageSubtypeId::TcEnableScheduling);
} }
#[test] #[test]
+990 -247
View File
File diff suppressed because it is too large Load Diff
+1440
View File
File diff suppressed because it is too large Load Diff
+1293 -304
View File
File diff suppressed because it is too large Load Diff
+2081
View File
File diff suppressed because it is too large Load Diff
+14 -4
View File
@@ -3,17 +3,27 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Message subtype ID.
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)] #[repr(u8)]
pub enum Subservice { pub enum MessageSubtypeId {
/// Telemetry - Acceptance success.
TmAcceptanceSuccess = 1, TmAcceptanceSuccess = 1,
/// Telemetry - Acceptance failure.
TmAcceptanceFailure = 2, TmAcceptanceFailure = 2,
/// Telemetry - Start success.
TmStartSuccess = 3, TmStartSuccess = 3,
/// Telemetry - Start failure.
TmStartFailure = 4, TmStartFailure = 4,
/// Telemetry - Step success.
TmStepSuccess = 5, TmStepSuccess = 5,
/// Telemetry - Step failure.
TmStepFailure = 6, TmStepFailure = 6,
/// Telemetry - Completion success.
TmCompletionSuccess = 7, TmCompletionSuccess = 7,
/// Telemetry - Completion failure.
TmCompletionFailure = 8, TmCompletionFailure = 8,
} }
@@ -23,13 +33,13 @@ mod tests {
#[test] #[test]
fn test_conv_into_u8() { fn test_conv_into_u8() {
let subservice: u8 = Subservice::TmCompletionSuccess.into(); let subservice: u8 = MessageSubtypeId::TmCompletionSuccess.into();
assert_eq!(subservice, 7); assert_eq!(subservice, 7);
} }
#[test] #[test]
fn test_conv_from_u8() { fn test_conv_from_u8() {
let subservice: Subservice = 7.try_into().unwrap(); let subservice: MessageSubtypeId = 7.try_into().unwrap();
assert_eq!(subservice, Subservice::TmCompletionSuccess); assert_eq!(subservice, MessageSubtypeId::TmCompletionSuccess);
} }
} }
+2185 -494
View File
File diff suppressed because it is too large Load Diff
+676 -98
View File
@@ -1,32 +1,50 @@
//! # Sequence counter module.
//!
//! CCSDS and ECSS packet standard oftentimes use sequence counters, for example to allow detecting
//! packet gaps. This module provides basic abstractions and helper components to implement
//! sequence counters.
use crate::MAX_SEQ_COUNT; use crate::MAX_SEQ_COUNT;
use arbitrary_int::traits::Integer;
use core::cell::Cell; use core::cell::Cell;
use paste::paste; use paste::paste;
#[cfg(feature = "std")]
pub use stdmod::*;
/// Core trait for objects which can provide a sequence count. /// Core trait for objects which can provide a sequence count.
/// ///
/// The core functions are not mutable on purpose to allow easier usage with /// The core functions are not mutable on purpose to allow easier usage with
/// static structs when using the interior mutability pattern. This can be achieved by using /// static structs when using the interior mutability pattern. This can be achieved by using
/// [Cell], [core::cell::RefCell] or atomic types. /// [Cell], [core::cell::RefCell] or atomic types.
pub trait SequenceCountProvider { pub trait SequenceCounter {
/// Raw type of the counter.
type Raw: Into<u64>; type Raw: Into<u64>;
const MAX_BIT_WIDTH: usize;
/// Bit width of the counter.
fn max_bit_width(&self) -> usize;
/// Get the current sequence count value.
fn get(&self) -> Self::Raw; fn get(&self) -> Self::Raw;
/// Increment the sequence count by one.
fn increment(&self); fn increment(&self);
/// Get the current sequence count value and increment the counter by one.
fn get_and_increment(&self) -> Self::Raw { fn get_and_increment(&self) -> Self::Raw {
let val = self.get(); let val = self.get();
self.increment(); self.increment();
val val
} }
/// Set the sequence counter.
///
/// This should not be required by default but can be used to reset the counter
/// or initialize it with a custom value.
fn set(&self, value: Self::Raw);
} }
/// Simple sequence counter which wraps at ´T::MAX´.
#[derive(Clone)] #[derive(Clone)]
pub struct SeqCountProviderSimple<T: Copy> { pub struct SequenceCounterSimple<T: Copy> {
seq_count: Cell<T>, seq_count: Cell<T>,
// The maximum value
max_val: T, max_val: T,
} }
@@ -34,14 +52,17 @@ macro_rules! impl_for_primitives {
($($ty: ident,)+) => { ($($ty: ident,)+) => {
$( $(
paste! { paste! {
impl SeqCountProviderSimple<$ty> { impl SequenceCounterSimple<$ty> {
pub fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self { /// Constructor with a custom maximum value.
pub const fn [<new_custom_max_val_ $ty>](max_val: $ty) -> Self {
Self { Self {
seq_count: Cell::new(0), seq_count: Cell::new(0),
max_val, max_val,
} }
} }
pub fn [<new_ $ty>]() -> Self {
/// Generic constructor.
pub const fn [<new_ $ty>]() -> Self {
Self { Self {
seq_count: Cell::new(0), seq_count: Cell::new(0),
max_val: $ty::MAX max_val: $ty::MAX
@@ -49,24 +70,31 @@ macro_rules! impl_for_primitives {
} }
} }
impl Default for SeqCountProviderSimple<$ty> { impl Default for SequenceCounterSimple<$ty> {
fn default() -> Self { fn default() -> Self {
Self::[<new_ $ty>]() Self::[<new_ $ty>]()
} }
} }
impl SequenceCountProvider for SeqCountProviderSimple<$ty> { impl SequenceCounter for SequenceCounterSimple<$ty> {
type Raw = $ty; type Raw = $ty;
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
#[inline]
fn max_bit_width(&self) -> usize {
core::mem::size_of::<Self::Raw>() * 8
}
#[inline]
fn get(&self) -> Self::Raw { fn get(&self) -> Self::Raw {
self.seq_count.get() self.seq_count.get()
} }
#[inline]
fn increment(&self) { fn increment(&self) {
self.get_and_increment(); self.get_and_increment();
} }
#[inline]
fn get_and_increment(&self) -> Self::Raw { fn get_and_increment(&self) -> Self::Raw {
let curr_count = self.seq_count.get(); let curr_count = self.seq_count.get();
@@ -77,6 +105,11 @@ macro_rules! impl_for_primitives {
} }
curr_count curr_count
} }
#[inline]
fn set(&self, value: Self::Raw) {
self.seq_count.set(value);
}
} }
} }
)+ )+
@@ -87,21 +120,21 @@ impl_for_primitives!(u8, u16, u32, u64,);
/// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT]. /// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT].
#[derive(Clone)] #[derive(Clone)]
pub struct CcsdsSimpleSeqCountProvider { pub struct SequenceCounterCcsdsSimple {
provider: SeqCountProviderSimple<u16>, provider: SequenceCounterSimple<u16>,
} }
impl Default for CcsdsSimpleSeqCountProvider { impl Default for SequenceCounterCcsdsSimple {
#[inline]
fn default() -> Self { fn default() -> Self {
Self { Self {
provider: SeqCountProviderSimple::new_custom_max_val_u16(MAX_SEQ_COUNT), provider: SequenceCounterSimple::new_custom_max_val_u16(MAX_SEQ_COUNT.as_u16()),
} }
} }
} }
impl SequenceCountProvider for CcsdsSimpleSeqCountProvider { impl SequenceCounter for SequenceCounterCcsdsSimple {
type Raw = u16; type Raw = u16;
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8;
delegate::delegate! { delegate::delegate! {
to self.provider { to self.provider {
fn get(&self) -> u16; fn get(&self) -> u16;
@@ -109,86 +142,510 @@ impl SequenceCountProvider for CcsdsSimpleSeqCountProvider {
fn get_and_increment(&self) -> u16; fn get_and_increment(&self) -> u16;
} }
} }
#[inline]
fn set(&self, value: u16) {
if value > MAX_SEQ_COUNT.as_u16() {
return;
}
self.provider.set(value);
}
#[inline]
fn max_bit_width(&self) -> usize {
Self::MAX_BIT_WIDTH
}
} }
#[cfg(feature = "std")] impl SequenceCounterCcsdsSimple {
pub mod stdmod { /// Maximum bit width for CCSDS packet sequence counter is 14 bits.
use super::*; pub const MAX_BIT_WIDTH: usize = 14;
use std::sync::{Arc, Mutex};
macro_rules! sync_clonable_seq_counter_impl { /// Create a new sequence counter specifically for the sequence count of CCSDS packets.
($($ty: ident,)+) => { ///
$(paste! { /// It has a [Self::MAX_BIT_WIDTH] of 14.
/// These sequence counters can be shared between threads and can also be pub const fn new() -> Self {
/// configured to wrap around at specified maximum values. Please note that Self {
/// that the API provided by this class will not panic und [Mutex] lock errors, provider: SequenceCounterSimple::new_custom_max_val_u16(MAX_SEQ_COUNT.value()),
/// but it will yield 0 for the getter functions. }
#[derive(Clone, Default)] }
pub struct [<SeqCountProviderSync $ty:upper>] { }
seq_count: Arc<Mutex<$ty>>,
max_val: $ty #[cfg(target_has_atomic = "8")]
} impl SequenceCounter for core::sync::atomic::AtomicU8 {
type Raw = u8;
impl [<SeqCountProviderSync $ty:upper>] {
pub fn new() -> Self { #[inline]
Self::new_with_max_val($ty::MAX) fn max_bit_width(&self) -> usize {
} 8
}
pub fn new_with_max_val(max_val: $ty) -> Self {
Self { #[inline]
seq_count: Arc::default(), fn get(&self) -> Self::Raw {
max_val self.load(core::sync::atomic::Ordering::Relaxed)
} }
}
} #[inline]
impl SequenceCountProvider for [<SeqCountProviderSync $ty:upper>] { fn increment(&self) {
type Raw = $ty; self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
const MAX_BIT_WIDTH: usize = core::mem::size_of::<Self::Raw>() * 8; }
fn get(&self) -> $ty { #[inline]
match self.seq_count.lock() { fn set(&self, value: u8) {
Ok(counter) => *counter, self.store(value, core::sync::atomic::Ordering::Relaxed);
Err(_) => 0 }
} }
}
#[cfg(target_has_atomic = "16")]
fn increment(&self) { impl SequenceCounter for core::sync::atomic::AtomicU16 {
self.get_and_increment(); type Raw = u16;
}
#[inline]
fn get_and_increment(&self) -> $ty { fn max_bit_width(&self) -> usize {
match self.seq_count.lock() { 16
Ok(mut counter) => { }
let val = *counter;
if val == self.max_val { #[inline]
*counter = 0; fn get(&self) -> Self::Raw {
} else { self.load(core::sync::atomic::Ordering::Relaxed)
*counter += 1; }
}
val #[inline]
} fn increment(&self) {
Err(_) => 0, self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
} }
}
} #[inline]
})+ fn set(&self, value: u16) {
} self.store(value, core::sync::atomic::Ordering::Relaxed);
}
}
#[cfg(target_has_atomic = "32")]
impl SequenceCounter for core::sync::atomic::AtomicU32 {
type Raw = u32;
#[inline]
fn max_bit_width(&self) -> usize {
32
}
#[inline]
fn get(&self) -> Self::Raw {
self.load(core::sync::atomic::Ordering::Relaxed)
}
#[inline]
fn increment(&self) {
self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn set(&self, value: u32) {
self.store(value, core::sync::atomic::Ordering::Relaxed);
}
}
#[cfg(target_has_atomic = "64")]
impl SequenceCounter for core::sync::atomic::AtomicU64 {
type Raw = u64;
#[inline]
fn max_bit_width(&self) -> usize {
64
}
#[inline]
fn get(&self) -> Self::Raw {
self.load(core::sync::atomic::Ordering::Relaxed)
}
#[inline]
fn increment(&self) {
self.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
#[inline]
fn set(&self, value: u64) {
self.store(value, core::sync::atomic::Ordering::Relaxed);
}
}
#[cfg(feature = "portable-atomic")]
impl SequenceCounter for portable_atomic::AtomicU8 {
type Raw = u8;
#[inline]
fn max_bit_width(&self) -> usize {
8
}
fn get(&self) -> Self::Raw {
self.load(portable_atomic::Ordering::Relaxed)
}
fn increment(&self) {
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
fn set(&self, value: Self::Raw) {
self.store(value, portable_atomic::Ordering::Relaxed);
}
}
#[cfg(feature = "portable-atomic")]
impl SequenceCounter for portable_atomic::AtomicU16 {
type Raw = u16;
#[inline]
fn max_bit_width(&self) -> usize {
16
}
fn get(&self) -> Self::Raw {
self.load(portable_atomic::Ordering::Relaxed)
}
fn increment(&self) {
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
fn set(&self, value: Self::Raw) {
self.store(value, portable_atomic::Ordering::Relaxed);
}
}
#[cfg(feature = "portable-atomic")]
impl SequenceCounter for portable_atomic::AtomicU32 {
type Raw = u32;
#[inline]
fn max_bit_width(&self) -> usize {
32
}
fn get(&self) -> Self::Raw {
self.load(portable_atomic::Ordering::Relaxed)
}
fn increment(&self) {
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
fn set(&self, value: Self::Raw) {
self.store(value, portable_atomic::Ordering::Relaxed);
}
}
#[cfg(feature = "portable-atomic")]
impl SequenceCounter for portable_atomic::AtomicU64 {
type Raw = u64;
#[inline]
fn max_bit_width(&self) -> usize {
64
}
fn get(&self) -> Self::Raw {
self.load(portable_atomic::Ordering::Relaxed)
}
fn increment(&self) {
self.fetch_add(1, portable_atomic::Ordering::Relaxed);
}
fn set(&self, value: Self::Raw) {
self.store(value, portable_atomic::Ordering::Relaxed);
}
}
impl<T: SequenceCounter + ?Sized> SequenceCounter for &T {
type Raw = T::Raw;
#[inline]
fn max_bit_width(&self) -> usize {
(**self).max_bit_width()
}
#[inline]
fn get(&self) -> Self::Raw {
(**self).get()
}
#[inline]
fn increment(&self) {
(**self).increment()
}
fn set(&self, value: Self::Raw) {
(**self).set(value);
}
}
#[cfg(any(
target_has_atomic = "8",
target_has_atomic = "16",
target_has_atomic = "32",
target_has_atomic = "64"
))]
macro_rules! sync_clonable_seq_counter_impl {
($ty: ident) => {
paste::paste! {
/// This can be used if a custom wrap value is required when using a thread-safe
/// atomic based sequence counter.
#[derive(Debug)]
pub struct [<SequenceCounterSyncCustomWrap $ty:upper>] {
seq_count: core::sync::atomic::[<Atomic $ty:upper>],
max_val: $ty,
}
impl [<SequenceCounterSyncCustomWrap $ty:upper>] {
/// Generic constructor.
pub fn new(max_val: $ty) -> Self {
Self {
seq_count: core::sync::atomic::[<Atomic $ty:upper>]::new(0),
max_val,
}
}
}
impl SequenceCounter for [<SequenceCounterSyncCustomWrap $ty:upper>] {
type Raw = $ty;
fn max_bit_width(&self) -> usize {
core::mem::size_of::<Self::Raw>() * 8
}
fn get(&self) -> $ty {
self.seq_count.load(core::sync::atomic::Ordering::Relaxed)
}
fn increment(&self) {
self.get_and_increment();
}
fn get_and_increment(&self) -> $ty {
self.seq_count.fetch_update(
core::sync::atomic::Ordering::Relaxed,
core::sync::atomic::Ordering::Relaxed,
|cur| {
// compute the next value, wrapping at MAX_VAL
let next = if cur == self.max_val { 0 } else { cur + 1 };
Some(next)
},
).unwrap()
}
fn set(&self, value: $ty) {
self.seq_count.store(value, core::sync::atomic::Ordering::Relaxed);
}
}
}
};
}
#[cfg(target_has_atomic = "8")]
sync_clonable_seq_counter_impl!(u8);
#[cfg(target_has_atomic = "16")]
sync_clonable_seq_counter_impl!(u16);
#[cfg(target_has_atomic = "32")]
sync_clonable_seq_counter_impl!(u32);
#[cfg(target_has_atomic = "64")]
sync_clonable_seq_counter_impl!(u64);
/// Modules relying on [std] support.
#[cfg(feature = "std")]
pub mod std_mod {
use super::*;
use core::str::FromStr;
use std::path::{Path, PathBuf};
use std::string::ToString as _;
use std::{fs, io};
/// A persistent file-backed sequence counter that can wrap any other [SequenceCounter]
/// implementation which is non-persistent.
///
/// In the default configuration, the underlying [SequenceCounter] is initialized from the file
/// content, and the file content will only be updated on a manual [Self::save] or on drop.
#[derive(Debug, PartialEq, Eq)]
pub struct SequenceCounterOnFile<
Inner: SequenceCounter<Raw = RawTy>,
RawTy: core::fmt::Debug
+ Copy
+ Clone
+ Into<u64>
+ TryFrom<u64>
+ FromStr
+ Default
+ PartialEq
+ Eq,
> {
path: PathBuf,
inner: Inner,
/// Configures whether the counter value is saved to disk when the object is dropped.
///
/// If this is set to [true] which is the default, the sequence counter will only be stored
/// to disk if the [Self::save] method is used or the object is dropped. Otherwise, the
/// counter will be saved to disk on every [Self::increment] or [Self::set].
pub save_on_drop: bool,
}
impl<
Inner: SequenceCounter<Raw = RawTy>,
RawTy: core::fmt::Debug
+ Copy
+ Clone
+ Into<u64>
+ TryFrom<u64>
+ FromStr
+ Default
+ PartialEq
+ Eq,
> SequenceCounterOnFile<Inner, RawTy>
{
/// Initialize a new persistent sequence counter using a file at the given path and
/// any non persistent inner [SequenceCounter] implementation.
pub fn new<P: AsRef<Path>>(path: P, inner: Inner) -> io::Result<Self> {
let path = path.as_ref().to_path_buf();
let value = Self::load_from_path(&path);
inner.set(value);
Ok(Self {
path,
inner,
save_on_drop: true,
})
}
fn load_from_path(path: &Path) -> RawTy {
let bytes = match fs::read(path) {
Ok(b) => b,
Err(_) => return Default::default(),
};
// Trim optional single trailing newline (Unix/Windows)
let trimmed = match bytes.last() {
Some(&b'\n') => &bytes[..bytes.len() - 1],
_ => &bytes,
};
// Reject non-ASCII
if !trimmed.is_ascii() {
return Default::default();
}
// Parse
std::str::from_utf8(trimmed)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or_default()
}
/// Persist the current value to disk (best-effort).
pub fn save(&self) -> io::Result<()> {
let value = self.inner.get();
std::fs::write(&self.path, value.into().to_string())
}
}
impl<
Inner: SequenceCounter<Raw = RawTy>,
RawTy: core::fmt::Debug
+ Copy
+ Clone
+ Into<u64>
+ TryFrom<u64, Error: core::fmt::Debug>
+ FromStr
+ Default
+ PartialEq
+ Eq,
> SequenceCounter for SequenceCounterOnFile<Inner, RawTy>
{
type Raw = RawTy;
fn max_bit_width(&self) -> usize {
self.inner.max_bit_width()
}
fn get(&self) -> RawTy {
self.inner.get()
}
fn increment(&self) {
self.inner.increment();
if !self.save_on_drop {
// persist (ignore I/O errors here; caller can call `save` explicitly)
let _ = self.save();
}
}
fn set(&self, value: RawTy) {
self.inner.set(value);
if !self.save_on_drop {
// persist (ignore I/O errors here; caller can call `save` explicitly)
let _ = self.save();
}
}
}
impl<
Inner: SequenceCounter<Raw = RawTy>,
RawTy: core::fmt::Debug
+ Copy
+ Clone
+ Into<u64>
+ TryFrom<u64>
+ FromStr
+ Default
+ PartialEq
+ Eq,
> Drop for SequenceCounterOnFile<Inner, RawTy>
{
fn drop(&mut self) {
if self.save_on_drop {
let _ = self.save();
}
}
}
/// Type alisas for a CCSDS sequence counter stored on file.
pub type SequenceCounterCcsdsOnFile = SequenceCounterOnFile<SequenceCounterCcsdsSimple, u16>;
impl SequenceCounterCcsdsOnFile {
/// Open or create the counter file at `path`.
pub fn new_ccsds_counter<P: AsRef<Path>>(path: P) -> io::Result<Self> {
SequenceCounterOnFile::new(path, SequenceCounterCcsdsSimple::default())
}
}
/// Type alisas for a [u16] sequence counter stored on file.
pub type SequenceCounterU16OnFile = SequenceCounterOnFile<SequenceCounterSimple<u16>, u16>;
impl SequenceCounterU16OnFile {
/// Open or create the counter file at `path`.
pub fn new_u16_counter<P: AsRef<Path>>(path: P) -> io::Result<Self> {
SequenceCounterOnFile::new(path, SequenceCounterSimple::<u16>::default())
}
} }
sync_clonable_seq_counter_impl!(u8, u16, u32, u64,);
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8};
use std::boxed::Box;
use crate::seq_count::{ use crate::seq_count::{
CcsdsSimpleSeqCountProvider, SeqCountProviderSimple, SeqCountProviderSyncU8, SequenceCounter, SequenceCounterCcsdsSimple, SequenceCounterSimple,
SequenceCountProvider, SequenceCounterSyncCustomWrapU8,
}; };
use crate::MAX_SEQ_COUNT; use crate::MAX_SEQ_COUNT;
#[test] #[test]
fn test_u8_counter() { fn test_u8_counter() {
let u8_counter = SeqCountProviderSimple::<u8>::default(); let u8_counter = SequenceCounterSimple::<u8>::default();
assert_eq!(u8_counter.get(), 0); assert_eq!(u8_counter.get(), 0);
assert_eq!(u8_counter.max_bit_width(), 8);
assert_eq!(u8_counter.get_and_increment(), 0); assert_eq!(u8_counter.get_and_increment(), 0);
assert_eq!(u8_counter.get_and_increment(), 1); assert_eq!(u8_counter.get_and_increment(), 1);
assert_eq!(u8_counter.get(), 2); assert_eq!(u8_counter.get(), 2);
@@ -196,7 +653,7 @@ mod tests {
#[test] #[test]
fn test_u8_counter_overflow() { fn test_u8_counter_overflow() {
let u8_counter = SeqCountProviderSimple::new_u8(); let u8_counter = SequenceCounterSimple::new_u8();
for _ in 0..256 { for _ in 0..256 {
u8_counter.increment(); u8_counter.increment();
} }
@@ -205,7 +662,7 @@ mod tests {
#[test] #[test]
fn test_ccsds_counter() { fn test_ccsds_counter() {
let ccsds_counter = CcsdsSimpleSeqCountProvider::default(); let ccsds_counter = SequenceCounterCcsdsSimple::default();
assert_eq!(ccsds_counter.get(), 0); assert_eq!(ccsds_counter.get(), 0);
assert_eq!(ccsds_counter.get_and_increment(), 0); assert_eq!(ccsds_counter.get_and_increment(), 0);
assert_eq!(ccsds_counter.get_and_increment(), 1); assert_eq!(ccsds_counter.get_and_increment(), 1);
@@ -214,37 +671,158 @@ mod tests {
#[test] #[test]
fn test_ccsds_counter_overflow() { fn test_ccsds_counter_overflow() {
let ccsds_counter = CcsdsSimpleSeqCountProvider::default(); let ccsds_counter = SequenceCounterCcsdsSimple::default();
for _ in 0..MAX_SEQ_COUNT + 1 { for _ in 0..MAX_SEQ_COUNT.value() + 1 {
ccsds_counter.increment(); ccsds_counter.increment();
} }
assert_eq!(ccsds_counter.get(), 0); assert_eq!(ccsds_counter.get(), 0);
} }
#[test] fn common_counter_test(seq_counter: &mut impl SequenceCounter) {
fn test_atomic_ref_counters() { assert_eq!(seq_counter.get().into(), 0);
let sync_u8_counter = SeqCountProviderSyncU8::new(); assert_eq!(seq_counter.get_and_increment().into(), 0);
assert_eq!(sync_u8_counter.get(), 0); assert_eq!(seq_counter.get_and_increment().into(), 1);
assert_eq!(sync_u8_counter.get_and_increment(), 0); assert_eq!(seq_counter.get().into(), 2);
assert_eq!(sync_u8_counter.get_and_increment(), 1); seq_counter.increment();
assert_eq!(sync_u8_counter.get(), 2); assert_eq!(seq_counter.get().into(), 3);
assert_eq!(seq_counter.get_and_increment().into(), 3);
assert_eq!(seq_counter.get().into(), 4);
} }
#[test] #[test]
fn test_atomic_ref_counters_overflow() { fn test_atomic_counter_u8() {
let sync_u8_counter = SeqCountProviderSyncU8::new(); let mut sync_u8_counter = AtomicU8::new(0);
assert_eq!(sync_u8_counter.max_bit_width(), 8);
common_counter_test(&mut sync_u8_counter);
}
#[test]
fn test_atomic_counter_u16() {
let mut sync_u16_counter = AtomicU16::new(0);
assert_eq!(sync_u16_counter.max_bit_width(), 16);
common_counter_test(&mut sync_u16_counter);
}
#[test]
fn test_atomic_counter_u32() {
let mut sync_u32_counter = AtomicU32::new(0);
assert_eq!(sync_u32_counter.max_bit_width(), 32);
common_counter_test(&mut sync_u32_counter);
}
#[test]
fn test_atomic_counter_u64() {
let mut sync_u64_counter = AtomicU64::new(0);
assert_eq!(sync_u64_counter.max_bit_width(), 64);
common_counter_test(&mut sync_u64_counter);
}
#[test]
#[cfg(feature = "portable-atomic")]
fn test_portable_atomic_counter_u8() {
let mut sync_u8_counter = portable_atomic::AtomicU8::new(0);
common_counter_test(&mut sync_u8_counter);
}
#[test]
#[cfg(feature = "portable-atomic")]
fn test_portable_atomic_counter_u16() {
let mut sync_u16_counter = portable_atomic::AtomicU16::new(0);
common_counter_test(&mut sync_u16_counter);
}
#[test]
#[cfg(feature = "portable-atomic")]
fn test_portable_atomic_counter_u32() {
let mut sync_u32_counter = portable_atomic::AtomicU32::new(0);
common_counter_test(&mut sync_u32_counter);
}
#[test]
#[cfg(feature = "portable-atomic")]
fn test_portable_atomic_counter_u64() {
let mut sync_u64_counter = portable_atomic::AtomicU64::new(0);
common_counter_test(&mut sync_u64_counter);
}
fn common_overflow_test_u8(seq_counter: &impl SequenceCounter) {
for _ in 0..u8::MAX as u16 + 1 { for _ in 0..u8::MAX as u16 + 1 {
sync_u8_counter.increment(); seq_counter.increment();
} }
assert_eq!(sync_u8_counter.get(), 0); assert_eq!(seq_counter.get().into(), 0);
}
#[test]
fn test_atomic_u8_counter_overflow() {
let sync_u8_counter = AtomicU8::new(0);
common_overflow_test_u8(&sync_u8_counter);
}
#[test]
#[cfg(feature = "portable-atomic")]
fn test_portable_atomic_u8_counter_overflow() {
let sync_u8_counter = portable_atomic::AtomicU8::new(0);
common_overflow_test_u8(&sync_u8_counter);
} }
#[test] #[test]
fn test_atomic_ref_counters_overflow_custom_max_val() { fn test_atomic_ref_counters_overflow_custom_max_val() {
let sync_u8_counter = SeqCountProviderSyncU8::new_with_max_val(128); let sync_u8_counter = SequenceCounterSyncCustomWrapU8::new(128);
for _ in 0..129 { for _ in 0..129 {
sync_u8_counter.increment(); sync_u8_counter.increment();
} }
assert_eq!(sync_u8_counter.get(), 0); assert_eq!(sync_u8_counter.get(), 0);
} }
#[test]
fn test_dyn_compatible() {
let counter: Box<dyn SequenceCounter<Raw = u16>> =
Box::new(SequenceCounterCcsdsSimple::default());
assert_eq!(counter.get(), 0);
assert_eq!(counter.max_bit_width(), 14);
counter.increment();
assert_eq!(counter.get(), 1);
assert_eq!(counter.get_and_increment(), 1);
assert_eq!(counter.get(), 2);
}
#[test]
fn test_persistent_counter() {
let tempdir = tempfile::tempdir().expect("failed to create temp dir");
let path = tempdir.path().join("seq_count.txt");
let mut persistent_counter =
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(&path)
.unwrap();
assert_eq!(persistent_counter.get(), 0);
assert_eq!(persistent_counter.get_and_increment(), 0);
drop(persistent_counter);
assert!(path.exists());
persistent_counter =
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(
tempdir.path().join("seq_count.txt"),
)
.unwrap();
assert_eq!(persistent_counter.get(), 1);
}
#[test]
fn test_persistent_couter_manual_save() {
let tempdir = tempfile::tempdir().expect("failed to create temp dir");
let path = tempdir.path().join("seq_count.txt");
let mut persistent_counter =
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(&path)
.unwrap();
assert_eq!(persistent_counter.get(), 0);
assert_eq!(persistent_counter.get_and_increment(), 0);
persistent_counter.save().unwrap();
assert!(path.exists());
std::mem::forget(persistent_counter);
persistent_counter =
crate::seq_count::std_mod::SequenceCounterCcsdsOnFile::new_ccsds_counter(
tempdir.path().join("seq_count.txt"),
)
.unwrap();
assert_eq!(persistent_counter.get(), 1);
}
} }
+1
View File
@@ -31,6 +31,7 @@ pub const FMT_STR_CODE_B_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3f", 21);
/// Three digits are used for the decimal fraction and a terminator is added at the end. /// 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); pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22);
/// Functions requiring both [chrono] and [alloc] support.
#[cfg(all(feature = "alloc", feature = "chrono"))] #[cfg(all(feature = "alloc", feature = "chrono"))]
pub mod alloc_mod_chrono { pub mod alloc_mod_chrono {
use super::*; use super::*;
+409 -433
View File
File diff suppressed because it is too large Load Diff
+47 -48
View File
@@ -6,7 +6,7 @@
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use core::fmt::{Debug, Display, Formatter}; use core::fmt::Debug;
use core::ops::{Add, AddAssign}; use core::ops::{Add, AddAssign};
use core::time::Duration; use core::time::Duration;
@@ -20,8 +20,6 @@ use super::{
TimestampError, UnixTime, TimestampError, UnixTime,
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::time::SystemTime; use std::time::SystemTime;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
@@ -34,6 +32,7 @@ pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::CucCcsdsEpoch as u8) << 4;
/// Maximum length if the preamble field is not extended. /// Maximum length if the preamble field is not extended.
pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8; pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8;
/// Fractional resolution for the fractional part of the CUC time code.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -72,13 +71,14 @@ pub fn convert_fractional_part_to_ns(fractional_part: FractionalPart) -> u64 {
10_u64.pow(9) * fractional_part.counter as u64 / div as u64 10_u64.pow(9) * fractional_part.counter as u64 / div as u64
} }
/// Convert the fractional resolution to the divisor used to calculate the fractional part.
#[inline(always)] #[inline(always)]
pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 { pub const fn fractional_res_to_div(res: FractionalResolution) -> u32 {
// We do not use the full possible range for a given resolution. This is because if we did // We do not use the full possible range for a given resolution. This is because if we did
// that, the largest value would be equal to the counter being incremented by one. Thus, the // that, the largest value would be equal to the counter being incremented by one. Thus, the
// smallest allowed fractions value is 0 while the largest allowed fractions value is the // smallest allowed fractions value is 0 while the largest allowed fractions value is the
// closest fractions value to the next counter increment. // closest fractions value to the next counter increment.
2_u32.pow(8 * res as u32) - 1 (1u32 << (8 * res as u32)) - 1
} }
/// Calculate the fractional part for a given resolution and subsecond nanoseconds. /// Calculate the fractional part for a given resolution and subsecond nanoseconds.
@@ -103,63 +103,36 @@ pub fn fractional_part_from_subsec_ns(res: FractionalResolution, ns: u64) -> Fra
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug)] /// CUC error.
#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CucError { pub enum CucError {
/// Invalid CUC counter width.
#[error("invalid cuc counter byte width {0}")]
InvalidCounterWidth(u8), InvalidCounterWidth(u8),
/// Invalid counter supplied. /// Invalid counter supplied.
#[error("invalid cuc counter {counter} for width {width}")]
InvalidCounter { InvalidCounter {
/// Width.
width: u8, width: u8,
/// Counter.
counter: u64, counter: u64,
}, },
/// Invalid fractions.
#[error("invalid cuc fractional part {value} for resolution {resolution:?}")]
InvalidFractions { InvalidFractions {
/// Resolution.
resolution: FractionalResolution, resolution: FractionalResolution,
/// Value.
value: u64, value: u64,
}, },
/// Error while correcting for leap seconds.
#[error("error while correcting for leap seconds")]
LeapSecondCorrectionError, LeapSecondCorrectionError,
DateBeforeCcsdsEpoch(DateBeforeCcsdsEpochError), /// Data is before the CCSDS epoch.
} #[error("date before ccsds epoch: {0}")]
DateBeforeCcsdsEpoch(#[from] DateBeforeCcsdsEpochError),
impl Display for CucError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
CucError::InvalidCounterWidth(w) => {
write!(f, "invalid cuc counter byte width {w}")
}
CucError::InvalidCounter { width, counter } => {
write!(f, "invalid cuc counter {counter} for width {width}")
}
CucError::InvalidFractions { resolution, value } => {
write!(
f,
"invalid cuc fractional part {value} for resolution {resolution:?}"
)
}
CucError::LeapSecondCorrectionError => {
write!(f, "error while correcting for leap seconds")
}
CucError::DateBeforeCcsdsEpoch(e) => {
write!(f, "date before ccsds epoch: {e}")
}
}
}
}
#[cfg(feature = "std")]
impl Error for CucError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
CucError::DateBeforeCcsdsEpoch(e) => Some(e),
_ => None,
}
}
}
impl From<DateBeforeCcsdsEpochError> for CucError {
fn from(e: DateBeforeCcsdsEpochError) -> Self {
Self::DateBeforeCcsdsEpoch(e)
}
} }
/// Tuple object where the first value is the width of the counter and the second value /// Tuple object where the first value is the width of the counter and the second value
@@ -168,14 +141,20 @@ impl From<DateBeforeCcsdsEpochError> for CucError {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WidthCounterPair(pub u8, pub u32); pub struct WidthCounterPair(pub u8, pub u32);
/// Fractional part of the CUC time code.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FractionalPart { pub struct FractionalPart {
/// Resolution.
pub resolution: FractionalResolution, pub resolution: FractionalResolution,
/// Counter.
pub counter: u32, pub counter: u32,
} }
impl FractionalPart { impl FractionalPart {
/// Generic constructor.
///
/// This function will panic if the counter is smaller than the calculated divisor.
#[inline] #[inline]
pub const fn new(resolution: FractionalResolution, counter: u32) -> Self { pub const fn new(resolution: FractionalResolution, counter: u32) -> Self {
let div = fractional_res_to_div(resolution); let div = fractional_res_to_div(resolution);
@@ -198,6 +177,7 @@ impl FractionalPart {
Self::new_with_seconds_resolution() Self::new_with_seconds_resolution()
} }
/// Check constructor which verifies that the counter is larger than the divisor.
#[inline] #[inline]
pub fn new_checked(resolution: FractionalResolution, counter: u32) -> Option<Self> { pub fn new_checked(resolution: FractionalResolution, counter: u32) -> Option<Self> {
let div = fractional_res_to_div(resolution); let div = fractional_res_to_div(resolution);
@@ -210,16 +190,19 @@ impl FractionalPart {
}) })
} }
/// Fractional resolution.
#[inline] #[inline]
pub fn resolution(&self) -> FractionalResolution { pub fn resolution(&self) -> FractionalResolution {
self.resolution self.resolution
} }
/// Counter value.
#[inline] #[inline]
pub fn counter(&self) -> u32 { pub fn counter(&self) -> u32 {
self.counter self.counter
} }
/// Check whether the timestamp does not have a fractional part.
#[inline] #[inline]
pub fn no_fractional_part(&self) -> bool { pub fn no_fractional_part(&self) -> bool {
self.resolution == FractionalResolution::Seconds self.resolution == FractionalResolution::Seconds
@@ -286,17 +269,21 @@ pub struct CucTime {
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CucTimeWithLeapSecs { pub struct CucTimeWithLeapSecs {
/// CUC time.
pub time: CucTime, pub time: CucTime,
/// Leap seconds.
pub leap_seconds: u32, pub leap_seconds: u32,
} }
impl CucTimeWithLeapSecs { impl CucTimeWithLeapSecs {
/// Generic constructor.
#[inline] #[inline]
pub fn new(time: CucTime, leap_seconds: u32) -> Self { pub fn new(time: CucTime, leap_seconds: u32) -> Self {
Self { time, leap_seconds } Self { time, leap_seconds }
} }
} }
/// p-field length.
#[inline] #[inline]
pub fn pfield_len(pfield: u8) -> usize { pub fn pfield_len(pfield: u8) -> usize {
if ((pfield >> 7) & 0b1) == 1 { if ((pfield >> 7) & 0b1) == 1 {
@@ -422,6 +409,7 @@ impl CucTime {
Ok(()) Ok(())
} }
/// Creates a CUC timestamp from a Chrono DateTime object.
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn from_chrono_date_time( pub fn from_chrono_date_time(
dt: &chrono::DateTime<chrono::Utc>, dt: &chrono::DateTime<chrono::Utc>,
@@ -464,7 +452,7 @@ impl CucTime {
.ok_or(CucError::LeapSecondCorrectionError)?; .ok_or(CucError::LeapSecondCorrectionError)?;
let fractions = let fractions =
fractional_part_from_subsec_ns(res, unix_time.subsec_millis() as u64 * 10_u64.pow(6)); fractional_part_from_subsec_ns(res, unix_time.subsec_millis() as u64 * 10_u64.pow(6));
Self::new_generic(WidthCounterPair(4, counter as u32), fractions) Self::new_generic(WidthCounterPair(4, counter), fractions)
} }
/// Most generic constructor which allows full configurability for the counter and for the /// Most generic constructor which allows full configurability for the counter and for the
@@ -489,21 +477,25 @@ impl CucTime {
}) })
} }
/// CCSDS time code.
#[inline] #[inline]
pub fn ccsds_time_code(&self) -> CcsdsTimeCode { pub fn ccsds_time_code(&self) -> CcsdsTimeCode {
CcsdsTimeCode::CucCcsdsEpoch CcsdsTimeCode::CucCcsdsEpoch
} }
/// Width and counter pair.
#[inline] #[inline]
pub fn width_counter_pair(&self) -> WidthCounterPair { pub fn width_counter_pair(&self) -> WidthCounterPair {
self.counter self.counter
} }
/// Counter width.
#[inline] #[inline]
pub fn counter_width(&self) -> u8 { pub fn counter_width(&self) -> u8 {
self.counter.0 self.counter.0
} }
/// Counter value.
#[inline] #[inline]
pub fn counter(&self) -> u32 { pub fn counter(&self) -> u32 {
self.counter.1 self.counter.1
@@ -515,11 +507,13 @@ impl CucTime {
self.fractions self.fractions
} }
/// Convert to the leap seconds helper.
#[inline] #[inline]
pub fn to_leap_sec_helper(&self, leap_seconds: u32) -> CucTimeWithLeapSecs { pub fn to_leap_sec_helper(&self, leap_seconds: u32) -> CucTimeWithLeapSecs {
CucTimeWithLeapSecs::new(*self, leap_seconds) CucTimeWithLeapSecs::new(*self, leap_seconds)
} }
/// Set the fractional part.
#[inline] #[inline]
pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> { pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> {
Self::verify_fractions_value(fractions)?; Self::verify_fractions_value(fractions)?;
@@ -566,16 +560,19 @@ impl CucTime {
self.pfield |= self.fractions.resolution() as u8; self.pfield |= self.fractions.resolution() as u8;
} }
/// Length of the counter from the p-field.
#[inline] #[inline]
pub fn len_cntr_from_pfield(pfield: u8) -> u8 { pub fn len_cntr_from_pfield(pfield: u8) -> u8 {
((pfield >> 2) & 0b11) + 1 ((pfield >> 2) & 0b11) + 1
} }
/// Length of the fractional part from the p-field.
#[inline] #[inline]
pub fn len_fractions_from_pfield(pfield: u8) -> u8 { pub fn len_fractions_from_pfield(pfield: u8) -> u8 {
pfield & 0b11 pfield & 0b11
} }
/// UNIX seconds.
#[inline] #[inline]
pub fn unix_secs(&self, leap_seconds: u32) -> i64 { pub fn unix_secs(&self, leap_seconds: u32) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as i64) ccsds_epoch_to_unix_epoch(self.counter.1 as i64)
@@ -583,6 +580,7 @@ impl CucTime {
.unwrap() .unwrap()
} }
/// Subsecond milliseconds part of the CUC time.
#[inline] #[inline]
pub fn subsec_millis(&self) -> u16 { pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16 (self.subsec_nanos() / 1_000_000) as u16
@@ -605,6 +603,7 @@ impl CucTime {
) )
} }
/// Packed length from the raw p-field.
#[inline] #[inline]
pub fn len_packed_from_pfield(pfield: u8) -> usize { pub fn len_packed_from_pfield(pfield: u8) -> usize {
let mut base_len: usize = 1; let mut base_len: usize = 1;
+75 -71
View File
@@ -3,7 +3,6 @@ use crate::ByteConversionError;
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign, Sub}; use core::ops::{Add, AddAssign, Sub};
use core::time::Duration; use core::time::Duration;
@@ -14,8 +13,6 @@ use num_traits::float::FloatCore;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError}; use std::time::{SystemTime, SystemTimeError};
#[cfg(feature = "std")] #[cfg(feature = "std")]
@@ -25,19 +22,29 @@ pub mod ascii;
pub mod cds; pub mod cds;
pub mod cuc; pub mod cuc;
/// Conversion constant for converting CCSDS days to UNIX days.
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
/// Seconds per day.
pub const SECONDS_PER_DAY: u32 = 86400; pub const SECONDS_PER_DAY: u32 = 86400;
/// Milliseconds per day.
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000; pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
/// Nanoseconds per second.
pub const NANOS_PER_SECOND: u32 = 1_000_000_000; pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
/// CCSDS time code identifiers.
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CcsdsTimeCode { pub enum CcsdsTimeCode {
/// CUC with a CCSDS epoch (1958-01-01T00:00:00+00:00).
CucCcsdsEpoch = 0b001, CucCcsdsEpoch = 0b001,
/// CUC with a custom agency epoch.
CucAgencyEpoch = 0b010, CucAgencyEpoch = 0b010,
/// CDS time code.
Cds = 0b100, Cds = 0b100,
/// CCS time code.
Ccs = 0b101, Ccs = 0b101,
/// Agency defined time code.
AgencyDefined = 0b110, AgencyDefined = 0b110,
} }
@@ -63,96 +70,61 @@ pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits) CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits)
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone)] /// Date is before the CCSDS epoch.
#[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("date before ccsds epoch: {0:?}")]
pub struct DateBeforeCcsdsEpochError(UnixTime); pub struct DateBeforeCcsdsEpochError(UnixTime);
impl Display for DateBeforeCcsdsEpochError { /// Generic timestamp error.
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { #[derive(Debug, PartialEq, Eq, Copy, Clone, thiserror::Error)]
write!(f, "date before ccsds epoch: {:?}", self.0)
}
}
#[cfg(feature = "std")]
impl Error for DateBeforeCcsdsEpochError {}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum TimestampError { pub enum TimestampError {
InvalidTimeCode { expected: CcsdsTimeCode, found: u8 }, /// Invalid time code.
ByteConversion(ByteConversionError), #[error("invalid time code, expected {expected:?}, found {found}")]
Cds(cds::CdsError), InvalidTimeCode {
Cuc(cuc::CucError), /// Expected time code.
expected: CcsdsTimeCode,
/// Found raw time code.
found: u8,
},
/// Byte conversion error.
#[error("time stamp: byte conversion error: {0}")]
ByteConversion(#[from] ByteConversionError),
/// CDS timestamp error.
#[error("CDS error: {0}")]
Cds(#[from] cds::CdsError),
/// CUC timestamp error.
#[error("CUC error: {0}")]
Cuc(#[from] cuc::CucError),
/// Custom epoch is not supported.
#[error("custom epoch not supported")]
CustomEpochNotSupported, CustomEpochNotSupported,
} }
impl Display for TimestampError { /// [std] module.
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
TimestampError::InvalidTimeCode { expected, found } => {
write!(
f,
"invalid raw time code value {found} for time code {expected:?}"
)
}
TimestampError::Cds(e) => {
write!(f, "cds error: {e}")
}
TimestampError::Cuc(e) => {
write!(f, "cuc error: {e}")
}
TimestampError::ByteConversion(e) => {
write!(f, "time stamp: {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::ByteConversion(e) => Some(e),
TimestampError::Cds(e) => Some(e),
TimestampError::Cuc(e) => Some(e),
_ => None,
}
}
}
impl From<cds::CdsError> for TimestampError {
fn from(e: cds::CdsError) -> Self {
TimestampError::Cds(e)
}
}
impl From<cuc::CucError> for TimestampError {
fn from(e: cuc::CucError) -> Self {
TimestampError::Cuc(e)
}
}
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod std_mod { pub mod std_mod {
use crate::time::TimestampError; use crate::time::TimestampError;
use std::time::SystemTimeError; use std::time::SystemTimeError;
use thiserror::Error; use thiserror::Error;
/// [std] timestamp error.
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum StdTimestampError { pub enum StdTimestampError {
/// System time error.
#[error("system time error: {0:?}")] #[error("system time error: {0:?}")]
SystemTime(#[from] SystemTimeError), SystemTime(#[from] SystemTimeError),
/// Generic timestamp error.
#[error("timestamp error: {0}")] #[error("timestamp error: {0}")]
Timestamp(#[from] TimestampError), Timestamp(#[from] TimestampError),
} }
} }
/// Seconds since epoch for the current system time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn seconds_since_epoch() -> f64 { pub fn seconds_since_epoch() -> f64 {
SystemTime::now() SystemTime::now()
@@ -186,16 +158,19 @@ 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) unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
} }
/// Convert CCSDS epoch to UNIX epoch.
#[inline] #[inline]
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> 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) ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
} }
/// Milliseconds of day for the current system time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn ms_of_day_using_sysclock() -> u32 { pub fn ms_of_day_using_sysclock() -> u32 {
ms_of_day(seconds_since_epoch()) ms_of_day(seconds_since_epoch())
} }
/// Milliseconds for the given seconds since epoch.
pub fn ms_of_day(seconds_since_epoch: f64) -> u32 { pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
let fraction_ms = seconds_since_epoch - seconds_since_epoch.floor(); 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 let ms_of_day: u32 = (((seconds_since_epoch.floor() as u32 % SECONDS_PER_DAY) * 1000) as f64
@@ -204,13 +179,16 @@ pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
ms_of_day ms_of_day
} }
/// Generic writable timestamp trait.
pub trait TimeWriter { pub trait TimeWriter {
/// Written length.
fn len_written(&self) -> usize; fn len_written(&self) -> usize;
/// Generic function to convert write a timestamp into a raw buffer. /// Generic function to convert write a timestamp into a raw buffer.
/// Returns the number of written bytes on success. /// Returns the number of written bytes on success.
fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>; fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>;
/// Convert to a owned [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> { fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> {
let mut vec = alloc::vec![0; self.len_written()]; let mut vec = alloc::vec![0; self.len_written()];
@@ -219,7 +197,9 @@ pub trait TimeWriter {
} }
} }
/// Genmeric readable timestamp trait.
pub trait TimeReader: Sized { pub trait TimeReader: Sized {
/// Create a timestamp from a raw byte buffer.
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>; fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>;
} }
@@ -229,6 +209,7 @@ pub trait TimeReader: Sized {
/// practical because they are a very common and simple exchange format for time information. /// practical because they are a very common and simple exchange format for time information.
/// Therefore, it was decided to keep them in this trait as well. /// Therefore, it was decided to keep them in this trait as well.
pub trait CcsdsTimeProvider { pub trait CcsdsTimeProvider {
/// Length when written to bytes.
fn len_as_bytes(&self) -> usize; fn len_as_bytes(&self) -> usize;
/// Returns the pfield of the time provider. The pfield can have one or two bytes depending /// Returns the pfield of the time provider. The pfield can have one or two bytes depending
@@ -236,24 +217,37 @@ pub trait CcsdsTimeProvider {
/// entry denotes the length of the pfield and the second entry is the value of the pfield /// entry denotes the length of the pfield and the second entry is the value of the pfield
/// in big endian format. /// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]); fn p_field(&self) -> (usize, [u8; 2]);
/// CCSDS time code field.
fn ccdsd_time_code(&self) -> CcsdsTimeCode; fn ccdsd_time_code(&self) -> CcsdsTimeCode;
fn unix_secs(&self) -> i64; /// UNIX time as seconds.
fn subsec_nanos(&self) -> u32; fn unix_secs(&self) -> i64 {
self.unix_time().secs
}
/// Subsecond nanoseconds.
fn subsec_nanos(&self) -> u32 {
self.unix_time().subsec_nanos
}
/// Subsecond milliseconds.
fn subsec_millis(&self) -> u16 { fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16 (self.subsec_nanos() / 1_000_000) as u16
} }
/// UNIX time.
fn unix_time(&self) -> UnixTime { fn unix_time(&self) -> UnixTime {
UnixTime::new(self.unix_secs(), self.subsec_nanos()) UnixTime::new(self.unix_secs(), self.subsec_nanos())
} }
/// [chrono] date time.
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> { fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos()) chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos())
} }
/// [time] library date] library date time.
#[cfg(feature = "timelib")] #[cfg(feature = "timelib")]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> { fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())? Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())?
@@ -335,6 +329,7 @@ impl UnixTime {
} }
} }
/// New UNIX time with only seconds, subseconds set to zero.
pub fn new_only_secs(unix_seconds: i64) -> Self { pub fn new_only_secs(unix_seconds: i64) -> Self {
Self { Self {
secs: unix_seconds, secs: unix_seconds,
@@ -342,15 +337,18 @@ impl UnixTime {
} }
} }
/// Sub-second milliseconds.
#[inline] #[inline]
pub fn subsec_millis(&self) -> u16 { pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos / 1_000_000) as u16 (self.subsec_nanos / 1_000_000) as u16
} }
/// Sub-second nanoseconds.
pub fn subsec_nanos(&self) -> u32 { pub fn subsec_nanos(&self) -> u32 {
self.subsec_nanos self.subsec_nanos
} }
/// Create a UNIX timestamp from the current system time.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn now() -> Result<Self, SystemTimeError> { pub fn now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
@@ -358,27 +356,31 @@ impl UnixTime {
Ok(Self::new(epoch as i64, now.subsec_nanos())) Ok(Self::new(epoch as i64, now.subsec_nanos()))
} }
/// UNIX timestamp as a floating point number in seconds.
#[inline] #[inline]
pub fn unix_secs_f64(&self) -> f64 { pub fn unix_secs_f64(&self) -> f64 {
self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0) self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0)
} }
/// UNIX timestamp as seconds, discards the sub-second part.
pub fn as_secs(&self) -> i64 { pub fn as_secs(&self) -> i64 {
self.secs self.secs
} }
/// UNIX timestamp as [chrono] date time.
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> { pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
Utc.timestamp_opt(self.secs, self.subsec_nanos) Utc.timestamp_opt(self.secs, self.subsec_nanos)
} }
/// UNIX timestamp as [time] library date time.
#[cfg(feature = "timelib")] #[cfg(feature = "timelib")]
pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> { pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.as_secs())? Ok(time::OffsetDateTime::from_unix_timestamp(self.as_secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into())) + time::Duration::nanoseconds(self.subsec_nanos().into()))
} }
// Calculate the difference in milliseconds between two UnixTimestamps /// Calculate the difference in milliseconds between two UnixTimestamps
pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> { pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> {
let seconds_difference = self.secs.checked_sub(other.secs)?; let seconds_difference = self.secs.checked_sub(other.secs)?;
// Convert seconds difference to milliseconds // Convert seconds difference to milliseconds
@@ -448,7 +450,9 @@ impl Ord for UnixTime {
/// so the sign information is supplied separately. /// so the sign information is supplied separately.
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct StampDiff { pub struct StampDiff {
/// Positive duration flag.
pub positive_duration: bool, pub positive_duration: bool,
/// Absolute duration.
pub duration_absolute: Duration, pub duration_absolute: Duration,
} }
@@ -743,7 +747,7 @@ mod tests {
fn test_cuc_error_printout() { fn test_cuc_error_printout() {
let cuc_error = CucError::InvalidCounterWidth(12); let cuc_error = CucError::InvalidCounterWidth(12);
let stamp_error = TimestampError::from(cuc_error); let stamp_error = TimestampError::from(cuc_error);
assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}")); assert_eq!(stamp_error.to_string(), format!("CUC error: {cuc_error}"));
} }
#[test] #[test]
+1510
View File
File diff suppressed because it is too large Load Diff
+48 -43
View File
@@ -1,14 +1,16 @@
//! # Utility module.
use crate::ByteConversionError; use crate::ByteConversionError;
use core::fmt::{Debug, Display, Formatter}; use core::fmt::Debug;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
/// Helper traits for types which can be converted to a byte array.
pub trait ToBeBytes { pub trait ToBeBytes {
/// Concrete byte array type.
type ByteArray: AsRef<[u8]>; type ByteArray: AsRef<[u8]>;
/// Length when written to big endian bytes. /// Length when written to big endian bytes.
fn written_len(&self) -> usize; fn written_len(&self) -> usize;
/// Convert to big endian byte array.
fn to_be_bytes(&self) -> Self::ByteArray; fn to_be_bytes(&self) -> Self::ByteArray;
} }
@@ -82,14 +84,17 @@ impl ToBeBytes for u64 {
} }
} }
/// Helper trait for unsigned enumerations.
pub trait UnsignedEnum { pub trait UnsignedEnum {
/// Size of the unsigned enumeration in bytes. /// Size of the unsigned enumeration in bytes.
fn size(&self) -> usize; fn size(&self) -> usize;
/// Write the unsigned enumeration to a raw buffer. Returns the written size on success. /// Write the unsigned enumeration to a raw buffer. Returns the written size on success.
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>; fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError>;
fn value(&self) -> u64; /// Type-erased raw value.
fn value_raw(&self) -> u64;
/// Convert to a [alloc::vec::Vec].
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn to_vec(&self) -> alloc::vec::Vec<u8> { fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0; self.size()]; let mut buf = alloc::vec![0; self.size()];
@@ -98,51 +103,36 @@ pub trait UnsignedEnum {
} }
} }
/// Extension trait for unsigned enumerations.
pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {} pub trait UnsignedEnumExt: UnsignedEnum + Debug + Copy + Clone + PartialEq + Eq {}
#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Unsigned byte field errors.
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UnsignedByteFieldError { pub enum UnsignedByteFieldError {
/// Value is too large for specified width of byte field. /// Value is too large for specified width of byte field.
#[error("value {value} too large for width {width}")]
ValueTooLargeForWidth { ValueTooLargeForWidth {
/// Width in bytes.
width: usize, width: usize,
/// Value.
value: u64, value: u64,
}, },
/// Only 1, 2, 4 and 8 are allow width values. Optionally contains the expected width if /// Only 1, 2, 4 and 8 are allow width values. Optionally contains the expected width if
/// applicable, for example for conversions. /// applicable, for example for conversions.
#[error("invalid width {found}, expected {expected:?}")]
InvalidWidth { InvalidWidth {
/// Found width.
found: usize, found: usize,
/// Expected width.
expected: Option<usize>, expected: Option<usize>,
}, },
ByteConversionError(ByteConversionError), /// Error during byte conversion.
#[error("byte conversion error: {0}")]
ByteConversionError(#[from] ByteConversionError),
} }
impl From<ByteConversionError> for UnsignedByteFieldError {
#[inline]
fn from(value: ByteConversionError) -> Self {
Self::ByteConversionError(value)
}
}
impl Display for UnsignedByteFieldError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::ByteConversionError(e) => {
write!(f, "low level byte conversion error: {e}")
}
Self::InvalidWidth { found, .. } => {
write!(f, "invalid width {found}, only 1, 2, 4 and 8 are allowed.")
}
Self::ValueTooLargeForWidth { width, value } => {
write!(f, "value {value} too large for width {width}")
}
}
}
}
#[cfg(feature = "std")]
impl Error for UnsignedByteFieldError {}
/// Type erased variant. /// Type erased variant.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -153,16 +143,19 @@ pub struct UnsignedByteField {
} }
impl UnsignedByteField { impl UnsignedByteField {
/// Generic constructor.
#[inline] #[inline]
pub const fn new(width: usize, value: u64) -> Self { pub const fn new(width: usize, value: u64) -> Self {
Self { width, value } Self { width, value }
} }
/// Type-erased raw value.
#[inline] #[inline]
pub const fn value_const(&self) -> u64 { pub const fn value(&self) -> u64 {
self.value self.value
} }
/// Construct from raw bytes, assuming big-endian byte order.
#[inline] #[inline]
pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result<Self, UnsignedByteFieldError> { pub fn new_from_be_bytes(width: usize, buf: &[u8]) -> Result<Self, UnsignedByteFieldError> {
if width > buf.len() { if width > buf.len() {
@@ -202,8 +195,8 @@ impl UnsignedEnum for UnsignedByteField {
} }
#[inline] #[inline]
fn value(&self) -> u64 { fn value_raw(&self) -> u64 {
self.value_const() self.value()
} }
fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> { fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
@@ -239,6 +232,7 @@ impl UnsignedEnum for UnsignedByteField {
} }
} }
/// Generic type erased unsigned byte field.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -247,11 +241,13 @@ pub struct GenericUnsignedByteField<TYPE: Copy + Into<u64>> {
} }
impl<TYPE: Copy + Into<u64>> GenericUnsignedByteField<TYPE> { impl<TYPE: Copy + Into<u64>> GenericUnsignedByteField<TYPE> {
/// Generic constructor.
pub const fn new(val: TYPE) -> Self { pub const fn new(val: TYPE) -> Self {
Self { value: val } Self { value: val }
} }
pub const fn value_typed(&self) -> TYPE { /// Raw value.
pub const fn value(&self) -> TYPE {
self.value self.value
} }
} }
@@ -274,20 +270,29 @@ impl<TYPE: Copy + ToBeBytes + Into<u64>> UnsignedEnum for GenericUnsignedByteFie
} }
#[inline] #[inline]
fn value(&self) -> u64 { fn value_raw(&self) -> u64 {
self.value_typed().into() self.value().into()
} }
} }
/// Alias for [GenericUnsignedByteField] with [()] generic.
pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>; pub type UnsignedByteFieldEmpty = GenericUnsignedByteField<()>;
/// Alias for [GenericUnsignedByteField] with [u8] generic.
pub type UnsignedByteFieldU8 = GenericUnsignedByteField<u8>; pub type UnsignedByteFieldU8 = GenericUnsignedByteField<u8>;
/// Alias for [GenericUnsignedByteField] with [u16] generic.
pub type UnsignedByteFieldU16 = GenericUnsignedByteField<u16>; pub type UnsignedByteFieldU16 = GenericUnsignedByteField<u16>;
/// Alias for [GenericUnsignedByteField] with [u32] generic.
pub type UnsignedByteFieldU32 = GenericUnsignedByteField<u32>; pub type UnsignedByteFieldU32 = GenericUnsignedByteField<u32>;
/// Alias for [GenericUnsignedByteField] with [u64] generic.
pub type UnsignedByteFieldU64 = GenericUnsignedByteField<u64>; pub type UnsignedByteFieldU64 = GenericUnsignedByteField<u64>;
/// Alias for [UnsignedByteFieldU8]
pub type UbfU8 = UnsignedByteFieldU8; pub type UbfU8 = UnsignedByteFieldU8;
/// Alias for [UnsignedByteFieldU16]
pub type UbfU16 = UnsignedByteFieldU16; pub type UbfU16 = UnsignedByteFieldU16;
/// Alias for [UnsignedByteFieldU32]
pub type UbfU32 = UnsignedByteFieldU32; pub type UbfU32 = UnsignedByteFieldU32;
/// Alias for [UnsignedByteFieldU64]
pub type UbfU64 = UnsignedByteFieldU64; pub type UbfU64 = UnsignedByteFieldU64;
impl From<UnsignedByteFieldU8> for UnsignedByteField { impl From<UnsignedByteFieldU8> for UnsignedByteField {
@@ -378,7 +383,7 @@ impl TryFrom<UnsignedByteField> for UnsignedByteFieldU64 {
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { mod tests {
use crate::util::{ use crate::util::{
UnsignedByteField, UnsignedByteFieldError, UnsignedByteFieldU16, UnsignedByteFieldU32, UnsignedByteField, UnsignedByteFieldError, UnsignedByteFieldU16, UnsignedByteFieldU32,
UnsignedByteFieldU64, UnsignedByteFieldU8, UnsignedEnum, UnsignedByteFieldU64, UnsignedByteFieldU8, UnsignedEnum,
@@ -399,7 +404,7 @@ pub mod tests {
for val in buf.iter().skip(1) { for val in buf.iter().skip(1) {
assert_eq!(*val, 0); assert_eq!(*val, 0);
} }
assert_eq!(u8.value_typed(), 5); assert_eq!(u8.value_raw(), 5);
assert_eq!(u8.value(), 5); assert_eq!(u8.value(), 5);
} }
@@ -417,7 +422,7 @@ pub mod tests {
for val in buf.iter().skip(2) { for val in buf.iter().skip(2) {
assert_eq!(*val, 0); assert_eq!(*val, 0);
} }
assert_eq!(u16.value_typed(), 3823); assert_eq!(u16.value_raw(), 3823);
assert_eq!(u16.value(), 3823); assert_eq!(u16.value(), 3823);
} }
@@ -435,7 +440,7 @@ pub mod tests {
(4..8).for_each(|i| { (4..8).for_each(|i| {
assert_eq!(buf[i], 0); assert_eq!(buf[i], 0);
}); });
assert_eq!(u32.value_typed(), 80932); assert_eq!(u32.value_raw(), 80932);
assert_eq!(u32.value(), 80932); assert_eq!(u32.value(), 80932);
} }
@@ -450,7 +455,7 @@ pub mod tests {
assert_eq!(len, 8); assert_eq!(len, 8);
let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap()); let raw_val = u64::from_be_bytes(buf[0..8].try_into().unwrap());
assert_eq!(raw_val, 5999999); assert_eq!(raw_val, 5999999);
assert_eq!(u64.value_typed(), 5999999); assert_eq!(u64.value_raw(), 5999999);
assert_eq!(u64.value(), 5999999); assert_eq!(u64.value(), 5999999);
} }