remove spacepackets to replace with submodule
This commit is contained in:
parent
d39bbeb044
commit
051efaf7cb
18
.idea/runConfigurations/Test_spacepackets.xml
Normal file
18
.idea/runConfigurations/Test_spacepackets.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Test spacepackets" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="test -p spacepackets" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<envs />
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -1,34 +0,0 @@
|
||||
[package]
|
||||
name = "spacepackets"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
description = "Generic implementations for various CCSDS and ECSS packet standards"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/spacepackets"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["no-std", "space", "packets", "ccsds", "ecss"]
|
||||
categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-support", "embedded"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
num = "0.4"
|
||||
serde = "1.0.137"
|
||||
zerocopy = "0.6.1"
|
||||
crc = "3.0.0"
|
||||
delegate = "0.7.0"
|
||||
|
||||
[dependencies.heapless]
|
||||
version = "0.7.14"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[dev-dependencies.postcard]
|
||||
version = "0.7.3"
|
||||
features = ["use-std"]
|
||||
|
||||
[features]
|
||||
|
||||
default = ["heapless", "alloc"]
|
||||
alloc = ["serde/alloc"]
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,3 +0,0 @@
|
||||
Generic implementations for various CCSDS and ECSS packet standards.
|
||||
|
||||
This software contains code developed at the University of Stuttgart.
|
@ -1,67 +0,0 @@
|
||||
use postcard::{from_bytes, to_stdvec};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zerocopy::byteorder::{I32, U16};
|
||||
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned};
|
||||
|
||||
#[derive(AsBytes, FromBytes, Unaligned, Debug, Eq, PartialEq)]
|
||||
#[repr(C, packed)]
|
||||
struct ZeroCopyTest {
|
||||
some_bool: u8,
|
||||
some_u16: U16<NetworkEndian>,
|
||||
some_i32: I32<NetworkEndian>,
|
||||
some_float: [u8; 4],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct PostcardTest {
|
||||
some_bool: u8,
|
||||
some_u16: u16,
|
||||
some_i32: i32,
|
||||
some_float: f32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct SliceSerTest<'slice> {
|
||||
some_u8: u8,
|
||||
some_u32: u32,
|
||||
some_slice: &'slice [u8],
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let pc_test = PostcardTest {
|
||||
some_bool: true as u8,
|
||||
some_u16: 0x42,
|
||||
some_i32: -200,
|
||||
some_float: 7.7_f32,
|
||||
};
|
||||
|
||||
let out = to_stdvec(&pc_test).unwrap();
|
||||
println!("{:#04x?}", out);
|
||||
|
||||
let sample_hk = ZeroCopyTest {
|
||||
some_bool: true as u8,
|
||||
some_u16: U16::from(0x42),
|
||||
some_i32: I32::from(-200),
|
||||
some_float: 7.7_f32.to_be_bytes(),
|
||||
};
|
||||
let mut slice = [0; 11];
|
||||
sample_hk.write_to(slice.as_mut_slice());
|
||||
println!("{:#04x?}", slice);
|
||||
|
||||
let ser_vec;
|
||||
{
|
||||
let test_buf = [0, 1, 2, 3];
|
||||
let test_with_slice = SliceSerTest {
|
||||
some_u8: 12,
|
||||
some_u32: 1,
|
||||
some_slice: test_buf.as_slice(),
|
||||
};
|
||||
ser_vec = to_stdvec(&test_with_slice).unwrap();
|
||||
println!("{:#04x?}", out);
|
||||
}
|
||||
|
||||
{
|
||||
let test_deser: SliceSerTest = from_bytes(ser_vec.as_slice()).unwrap();
|
||||
println!("{:?}", test_deser);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use crate::{CcsdsPacket, PacketError};
|
||||
use core::mem::size_of;
|
||||
use crc::{Crc, CRC_16_IBM_3740};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// CRC algorithm used by the PUS standard
|
||||
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
|
||||
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
|
||||
|
||||
/// All PUS versions. Only PUS C is supported by this library
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum PusVersion {
|
||||
EsaPus = 0,
|
||||
PusA = 1,
|
||||
PusC = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum PusError {
|
||||
VersionNotSupported(PusVersion),
|
||||
IncorrectCrc(u16),
|
||||
RawDataTooShort(usize),
|
||||
NoRawData,
|
||||
/// CRC16 needs to be calculated first
|
||||
CrcCalculationMissing,
|
||||
OtherPacketError(PacketError),
|
||||
}
|
||||
|
||||
pub trait PusPacket: CcsdsPacket {
|
||||
const PUS_VERSION: PusVersion = PusVersion::PusC;
|
||||
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
|
||||
fn user_data(&self) -> Option<&[u8]>;
|
||||
fn crc16(&self) -> Option<u16>;
|
||||
}
|
@ -1,586 +0,0 @@
|
||||
//! # Space related components including CCSDS and ECSS packet standards
|
||||
#![no_std]
|
||||
extern crate alloc;
|
||||
|
||||
use crate::ecss::CCSDS_HEADER_LEN;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod ecss;
|
||||
pub mod tc;
|
||||
pub mod tm;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum PacketError {
|
||||
/// The passed slice is too small. Returns the required size of the failed size chgeck
|
||||
ToBytesSliceTooSmall(usize),
|
||||
/// The [zerocopy] library failed to write to bytes
|
||||
ToBytesZeroCopyError,
|
||||
FromBytesZeroCopyError,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub enum PacketType {
|
||||
Tm = 0,
|
||||
Tc = 1,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PacketType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == PacketType::Tm as u8 => Ok(PacketType::Tm),
|
||||
x if x == PacketType::Tc as u8 => Ok(PacketType::Tc),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packet_type_in_raw_packet_id(packet_id: u16) -> PacketType {
|
||||
PacketType::try_from((packet_id >> 12) as u8 & 0b1).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub enum SequenceFlags {
|
||||
ContinuationSegment = 0b00,
|
||||
FirstSegment = 0b01,
|
||||
LastSegment = 0b10,
|
||||
Unsegmented = 0b11,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SequenceFlags {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == SequenceFlags::ContinuationSegment as u8 => {
|
||||
Ok(SequenceFlags::ContinuationSegment)
|
||||
}
|
||||
x if x == SequenceFlags::FirstSegment as u8 => Ok(SequenceFlags::FirstSegment),
|
||||
x if x == SequenceFlags::LastSegment as u8 => Ok(SequenceFlags::LastSegment),
|
||||
x if x == SequenceFlags::Unsegmented as u8 => Ok(SequenceFlags::Unsegmented),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub struct PacketId {
|
||||
pub ptype: PacketType,
|
||||
pub sec_header_flag: bool,
|
||||
apid: u16,
|
||||
}
|
||||
|
||||
impl PacketId {
|
||||
pub fn new(ptype: PacketType, sec_header_flag: bool, apid: u16) -> Option<PacketId> {
|
||||
let mut pid = PacketId {
|
||||
ptype,
|
||||
sec_header_flag,
|
||||
apid: 0,
|
||||
};
|
||||
pid.set_apid(apid).then(|| pid)
|
||||
}
|
||||
|
||||
pub fn set_apid(&mut self, apid: u16) -> bool {
|
||||
if apid > num::pow(2, 11) - 1 {
|
||||
return false;
|
||||
}
|
||||
self.apid = apid;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn apid(&self) -> u16 {
|
||||
self.apid
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> u16 {
|
||||
((self.ptype as u16) << 12) | ((self.sec_header_flag as u16) << 11) | self.apid
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for PacketId {
|
||||
fn from(raw_id: u16) -> Self {
|
||||
PacketId {
|
||||
ptype: PacketType::try_from(((raw_id >> 12) & 0b1) as u8).unwrap(),
|
||||
sec_header_flag: ((raw_id >> 11) & 0b1) != 0,
|
||||
apid: raw_id & 0x7FF,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub struct PacketSequenceCtrl {
|
||||
pub seq_flags: SequenceFlags,
|
||||
ssc: u16,
|
||||
}
|
||||
|
||||
impl PacketSequenceCtrl {
|
||||
pub fn new(seq_flags: SequenceFlags, ssc: u16) -> Option<PacketSequenceCtrl> {
|
||||
let mut psc = PacketSequenceCtrl { seq_flags, ssc: 0 };
|
||||
psc.set_ssc(ssc).then(|| psc)
|
||||
}
|
||||
pub fn raw(&self) -> u16 {
|
||||
((self.seq_flags as u16) << 14) | self.ssc
|
||||
}
|
||||
|
||||
pub fn set_ssc(&mut self, ssc: u16) -> bool {
|
||||
if ssc > num::pow(2, 14) - 1 {
|
||||
return false;
|
||||
}
|
||||
self.ssc = ssc;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn ssc(&self) -> u16 {
|
||||
self.ssc
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for PacketSequenceCtrl {
|
||||
fn from(raw_id: u16) -> Self {
|
||||
PacketSequenceCtrl {
|
||||
seq_flags: SequenceFlags::try_from(((raw_id >> 14) & 0b11) as u8).unwrap(),
|
||||
ssc: raw_id & SSC_MASK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! sph_from_other {
|
||||
($Self: path, $other: path) => {
|
||||
impl From<$other> for $Self {
|
||||
fn from(other: $other) -> Self {
|
||||
Self::from_composite_fields(
|
||||
other.packet_id(),
|
||||
other.psc(),
|
||||
other.data_len(),
|
||||
Some(other.ccsds_version()),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const SSC_MASK: u16 = 0x3FFF;
|
||||
const VERSION_MASK: u16 = 0xE000;
|
||||
|
||||
/// Generic trait to access fields of a CCSDS space packet header according to CCSDS 133.0-B-2
|
||||
pub trait CcsdsPacket {
|
||||
fn ccsds_version(&self) -> u8;
|
||||
fn packet_id(&self) -> PacketId;
|
||||
fn psc(&self) -> PacketSequenceCtrl;
|
||||
|
||||
/// Retrieve data length field
|
||||
fn data_len(&self) -> u16;
|
||||
/// Retrieve the total packet size based on the data length field
|
||||
fn total_len(&self) -> usize {
|
||||
usize::from(self.data_len()) + CCSDS_HEADER_LEN + 1
|
||||
}
|
||||
|
||||
/// Retrieve 13 bit Packet Identification field. Can usually be retrieved with a bitwise AND
|
||||
/// of the first 2 bytes with 0x1FFF
|
||||
#[inline]
|
||||
fn packet_id_raw(&self) -> u16 {
|
||||
self.packet_id().raw()
|
||||
}
|
||||
/// Retrieve Packet Sequence Count
|
||||
#[inline]
|
||||
fn psc_raw(&self) -> u16 {
|
||||
self.psc().raw()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Retrieve Packet Type (TM: 0, TC: 1)
|
||||
fn ptype(&self) -> PacketType {
|
||||
// This call should never fail because only 0 and 1 can be passed to the try_from call
|
||||
self.packet_id().ptype
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_tm(&self) -> bool {
|
||||
self.ptype() == PacketType::Tm
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_tc(&self) -> bool {
|
||||
self.ptype() == PacketType::Tc
|
||||
}
|
||||
|
||||
/// Retrieve the secondary header flag. Returns true if a secondary header is present
|
||||
/// and false if it is not
|
||||
#[inline]
|
||||
fn sec_header_flag(&self) -> bool {
|
||||
self.packet_id().sec_header_flag
|
||||
}
|
||||
|
||||
/// Retrieve Application Process ID
|
||||
#[inline]
|
||||
fn apid(&self) -> u16 {
|
||||
self.packet_id().apid
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ssc(&self) -> u16 {
|
||||
self.psc().ssc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sequence_flags(&self) -> SequenceFlags {
|
||||
// This call should never fail because the mask ensures that only valid values are passed
|
||||
// into the try_from function
|
||||
self.psc().seq_flags
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CcsdsPrimaryHeader {
|
||||
fn from_composite_fields(
|
||||
packet_id: PacketId,
|
||||
psc: PacketSequenceCtrl,
|
||||
data_len: u16,
|
||||
version: Option<u8>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
pub mod ser {
|
||||
use crate::{
|
||||
CcsdsPacket, CcsdsPrimaryHeader, PacketId, PacketSequenceCtrl, PacketType, SequenceFlags,
|
||||
};
|
||||
|
||||
/// Space Packet Primary Header according to CCSDS 133.0-B-2
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub struct SpHeader {
|
||||
pub version: u8,
|
||||
pub packet_id: PacketId,
|
||||
pub psc: PacketSequenceCtrl,
|
||||
pub data_len: u16,
|
||||
}
|
||||
impl Default for SpHeader {
|
||||
fn default() -> Self {
|
||||
SpHeader {
|
||||
version: 0,
|
||||
packet_id: PacketId {
|
||||
ptype: PacketType::Tm,
|
||||
apid: 0,
|
||||
sec_header_flag: true,
|
||||
},
|
||||
psc: PacketSequenceCtrl {
|
||||
seq_flags: SequenceFlags::Unsegmented,
|
||||
ssc: 0,
|
||||
},
|
||||
data_len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SpHeader {
|
||||
pub fn new(apid: u16, ptype: PacketType, ssc: u16, data_len: u16) -> Option<Self> {
|
||||
if ssc > num::pow(2, 14) - 1 || apid > num::pow(2, 11) - 1 {
|
||||
return None;
|
||||
}
|
||||
let mut header = SpHeader::default();
|
||||
header.packet_id.apid = apid;
|
||||
header.packet_id.ptype = ptype;
|
||||
header.psc.ssc = ssc;
|
||||
header.data_len = data_len;
|
||||
Some(header)
|
||||
}
|
||||
|
||||
pub fn tm(apid: u16, ssc: u16, data_len: u16) -> Option<Self> {
|
||||
Self::new(apid, PacketType::Tm, ssc, data_len)
|
||||
}
|
||||
|
||||
pub fn tc(apid: u16, ssc: u16, data_len: u16) -> Option<Self> {
|
||||
Self::new(apid, PacketType::Tc, ssc, data_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for SpHeader {
|
||||
#[inline]
|
||||
fn ccsds_version(&self) -> u8 {
|
||||
self.version
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn packet_id(&self) -> PacketId {
|
||||
self.packet_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn psc(&self) -> PacketSequenceCtrl {
|
||||
self.psc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn data_len(&self) -> u16 {
|
||||
self.data_len
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPrimaryHeader for SpHeader {
|
||||
fn from_composite_fields(
|
||||
packet_id: PacketId,
|
||||
psc: PacketSequenceCtrl,
|
||||
data_len: u16,
|
||||
version: Option<u8>,
|
||||
) -> Self {
|
||||
let mut version_to_set = 0b000;
|
||||
if let Some(version) = version {
|
||||
version_to_set = version;
|
||||
}
|
||||
SpHeader {
|
||||
version: version_to_set,
|
||||
packet_id,
|
||||
psc,
|
||||
data_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sph_from_other!(SpHeader, crate::zc::SpHeader);
|
||||
}
|
||||
|
||||
pub mod zc {
|
||||
use crate::{CcsdsPacket, CcsdsPrimaryHeader, PacketId, PacketSequenceCtrl, VERSION_MASK};
|
||||
use zerocopy::byteorder::NetworkEndian;
|
||||
use zerocopy::{AsBytes, FromBytes, Unaligned, U16};
|
||||
|
||||
#[derive(FromBytes, AsBytes, Unaligned, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct SpHeader {
|
||||
version_packet_id: U16<NetworkEndian>,
|
||||
psc: U16<NetworkEndian>,
|
||||
data_len: U16<NetworkEndian>,
|
||||
}
|
||||
|
||||
impl SpHeader {
|
||||
pub fn new(
|
||||
packet_id: PacketId,
|
||||
psc: PacketSequenceCtrl,
|
||||
data_len: u16,
|
||||
version: Option<u8>,
|
||||
) -> Self {
|
||||
let mut version_packet_id = packet_id.raw();
|
||||
if let Some(version) = version {
|
||||
version_packet_id = ((version as u16) << 13) | packet_id.raw()
|
||||
}
|
||||
SpHeader {
|
||||
version_packet_id: U16::from(version_packet_id),
|
||||
psc: U16::from(psc.raw()),
|
||||
data_len: U16::from(data_len),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(slice: &(impl AsRef<[u8]> + ?Sized)) -> Option<Self> {
|
||||
SpHeader::read_from(slice.as_ref())
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self, slice: &mut (impl AsMut<[u8]> + ?Sized)) -> Option<()> {
|
||||
self.write_to(slice.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPacket for SpHeader {
|
||||
#[inline]
|
||||
fn ccsds_version(&self) -> u8 {
|
||||
((self.version_packet_id.get() >> 13) as u8) & 0b111
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> PacketId {
|
||||
PacketId::from(self.packet_id_raw())
|
||||
}
|
||||
fn psc(&self) -> PacketSequenceCtrl {
|
||||
PacketSequenceCtrl::from(self.psc_raw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn data_len(&self) -> u16 {
|
||||
self.data_len.get()
|
||||
}
|
||||
|
||||
fn packet_id_raw(&self) -> u16 {
|
||||
self.version_packet_id.get() & (!VERSION_MASK)
|
||||
}
|
||||
|
||||
fn psc_raw(&self) -> u16 {
|
||||
self.psc.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl CcsdsPrimaryHeader for SpHeader {
|
||||
fn from_composite_fields(
|
||||
packet_id: PacketId,
|
||||
psc: PacketSequenceCtrl,
|
||||
data_len: u16,
|
||||
version: Option<u8>,
|
||||
) -> Self {
|
||||
SpHeader::new(packet_id, psc, data_len, version)
|
||||
}
|
||||
}
|
||||
|
||||
sph_from_other!(SpHeader, crate::ser::SpHeader);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ser::SpHeader;
|
||||
use crate::{
|
||||
packet_type_in_raw_packet_id, zc, CcsdsPacket, CcsdsPrimaryHeader, PacketId,
|
||||
PacketSequenceCtrl, PacketType, SequenceFlags,
|
||||
};
|
||||
use alloc::vec;
|
||||
use postcard::{from_bytes, to_stdvec};
|
||||
|
||||
#[test]
|
||||
fn test_helpers() {
|
||||
assert_eq!(
|
||||
SequenceFlags::try_from(0b00).expect("SEQ flag creation failed"),
|
||||
SequenceFlags::ContinuationSegment
|
||||
);
|
||||
assert_eq!(
|
||||
SequenceFlags::try_from(0b01).expect("SEQ flag creation failed"),
|
||||
SequenceFlags::FirstSegment
|
||||
);
|
||||
assert_eq!(
|
||||
SequenceFlags::try_from(0b10).expect("SEQ flag creation failed"),
|
||||
SequenceFlags::LastSegment
|
||||
);
|
||||
assert_eq!(
|
||||
SequenceFlags::try_from(0b11).expect("SEQ flag creation failed"),
|
||||
SequenceFlags::Unsegmented
|
||||
);
|
||||
assert!(SequenceFlags::try_from(0b100).is_err());
|
||||
assert!(PacketType::try_from(0b10).is_err());
|
||||
let packet_id =
|
||||
PacketId::new(PacketType::Tm, false, 0x42).expect("Packet ID creation failed");
|
||||
assert_eq!(packet_id.raw(), 0x0042);
|
||||
let packet_id_from_raw = PacketId::from(packet_id.raw());
|
||||
assert_eq!(
|
||||
packet_type_in_raw_packet_id(packet_id.raw()),
|
||||
PacketType::Tm
|
||||
);
|
||||
assert_eq!(packet_id_from_raw, packet_id);
|
||||
|
||||
let packet_id_invalid = PacketId::new(PacketType::Tc, true, 0xFFFF);
|
||||
assert!(packet_id_invalid.is_none());
|
||||
let packet_id_from_new = PacketId::new(PacketType::Tm, false, 0x42).unwrap();
|
||||
assert_eq!(packet_id_from_new, packet_id);
|
||||
let mut psc = PacketSequenceCtrl::new(SequenceFlags::ContinuationSegment, 77)
|
||||
.expect("PSC creation failed");
|
||||
assert_eq!(psc.raw(), 77);
|
||||
let psc_from_raw = PacketSequenceCtrl::from(psc.raw());
|
||||
assert_eq!(psc_from_raw, psc);
|
||||
// Fails because SSC is limited to 14 bits
|
||||
assert!(!psc.set_ssc(num::pow(2, 15)));
|
||||
assert_eq!(psc.raw(), 77);
|
||||
|
||||
let psc_invalid = PacketSequenceCtrl::new(SequenceFlags::FirstSegment, 0xFFFF);
|
||||
assert!(psc_invalid.is_none());
|
||||
let psc_from_new = PacketSequenceCtrl::new(SequenceFlags::ContinuationSegment, 77).unwrap();
|
||||
assert_eq!(psc_from_new, psc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_sph() {
|
||||
let sp_header = SpHeader::tc(0x42, 12, 0).expect("Error creating SP header");
|
||||
assert_eq!(sp_header.ccsds_version(), 0b000);
|
||||
assert!(sp_header.is_tc());
|
||||
assert!(sp_header.sec_header_flag());
|
||||
assert_eq!(sp_header.ptype(), PacketType::Tc);
|
||||
assert_eq!(sp_header.ssc(), 12);
|
||||
assert_eq!(sp_header.apid(), 0x42);
|
||||
assert_eq!(sp_header.sequence_flags(), SequenceFlags::Unsegmented);
|
||||
assert_eq!(sp_header.data_len(), 0);
|
||||
let output = to_stdvec(&sp_header).unwrap();
|
||||
let sp_header: SpHeader = from_bytes(&output).unwrap();
|
||||
assert_eq!(sp_header.version, 0b000);
|
||||
assert!(sp_header.packet_id.sec_header_flag);
|
||||
assert_eq!(sp_header.ptype(), PacketType::Tc);
|
||||
assert_eq!(sp_header.ssc(), 12);
|
||||
assert_eq!(sp_header.apid(), 0x42);
|
||||
assert_eq!(sp_header.sequence_flags(), SequenceFlags::Unsegmented);
|
||||
assert_eq!(sp_header.packet_id_raw(), 0x1842);
|
||||
assert_eq!(sp_header.psc_raw(), 0xC00C);
|
||||
assert_eq!(sp_header.ccsds_version(), 0b000);
|
||||
assert_eq!(sp_header.data_len, 0);
|
||||
|
||||
let sp_header = SpHeader::tm(0x7, 22, 36).expect("Error creating SP header");
|
||||
assert_eq!(sp_header.ccsds_version(), 0b000);
|
||||
assert!(sp_header.is_tm());
|
||||
assert!(sp_header.sec_header_flag());
|
||||
assert_eq!(sp_header.ptype(), PacketType::Tm);
|
||||
assert_eq!(sp_header.ssc(), 22);
|
||||
assert_eq!(sp_header.apid(), 0x07);
|
||||
assert_eq!(sp_header.sequence_flags(), SequenceFlags::Unsegmented);
|
||||
assert_eq!(sp_header.packet_id_raw(), 0x0807);
|
||||
assert_eq!(sp_header.psc_raw(), 0xC016);
|
||||
assert_eq!(sp_header.data_len(), 36);
|
||||
assert_eq!(sp_header.ccsds_version(), 0b000);
|
||||
|
||||
let from_comp_fields = SpHeader::from_composite_fields(
|
||||
PacketId::new(PacketType::Tc, true, 0x42).unwrap(),
|
||||
PacketSequenceCtrl::new(SequenceFlags::Unsegmented, 0x7).unwrap(),
|
||||
0,
|
||||
None,
|
||||
);
|
||||
assert_eq!(from_comp_fields.ptype(), PacketType::Tc);
|
||||
assert_eq!(from_comp_fields.apid(), 0x42);
|
||||
assert!(from_comp_fields.sec_header_flag());
|
||||
assert_eq!(
|
||||
from_comp_fields.sequence_flags(),
|
||||
SequenceFlags::Unsegmented
|
||||
);
|
||||
assert_eq!(from_comp_fields.ssc(), 0x7);
|
||||
assert_eq!(from_comp_fields.data_len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zc_sph() {
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
let sp_header =
|
||||
SpHeader::tc(0x7FF, num::pow(2, 14) - 1, 0).expect("Error creating SP header");
|
||||
assert_eq!(sp_header.ptype(), PacketType::Tc);
|
||||
assert_eq!(sp_header.apid(), 0x7FF);
|
||||
assert_eq!(sp_header.data_len(), 0);
|
||||
assert_eq!(sp_header.ccsds_version(), 0b000);
|
||||
assert!(sp_header.is_tc());
|
||||
let sp_header_zc = zc::SpHeader::from(sp_header);
|
||||
let slice = sp_header_zc.as_bytes();
|
||||
assert_eq!(slice.len(), 6);
|
||||
assert_eq!(slice[0], 0x1F);
|
||||
assert_eq!(slice[1], 0xFF);
|
||||
assert_eq!(slice[2], 0xFF);
|
||||
assert_eq!(slice[3], 0xFF);
|
||||
assert_eq!(slice[4], 0x00);
|
||||
assert_eq!(slice[5], 0x00);
|
||||
|
||||
let mut slice = [0; 6];
|
||||
sp_header_zc.write_to(slice.as_mut_slice());
|
||||
assert_eq!(slice.len(), 6);
|
||||
assert_eq!(slice[0], 0x1F);
|
||||
assert_eq!(slice[1], 0xFF);
|
||||
assert_eq!(slice[2], 0xFF);
|
||||
assert_eq!(slice[3], 0xFF);
|
||||
assert_eq!(slice[4], 0x00);
|
||||
assert_eq!(slice[5], 0x00);
|
||||
|
||||
let mut test_vec = vec![0_u8; 6];
|
||||
let slice = test_vec.as_mut_slice();
|
||||
sp_header_zc.write_to(slice);
|
||||
let slice = test_vec.as_slice();
|
||||
assert_eq!(slice.len(), 6);
|
||||
assert_eq!(slice[0], 0x1F);
|
||||
assert_eq!(slice[1], 0xFF);
|
||||
assert_eq!(slice[2], 0xFF);
|
||||
assert_eq!(slice[3], 0xFF);
|
||||
assert_eq!(slice[4], 0x00);
|
||||
assert_eq!(slice[5], 0x00);
|
||||
|
||||
let sp_header = zc::SpHeader::from_bytes(slice);
|
||||
assert!(sp_header.is_some());
|
||||
let sp_header = sp_header.unwrap();
|
||||
assert_eq!(sp_header.ccsds_version(), 0b000);
|
||||
assert_eq!(sp_header.packet_id_raw(), 0x1FFF);
|
||||
assert_eq!(sp_header.apid(), 0x7FF);
|
||||
assert_eq!(sp_header.ptype(), PacketType::Tc);
|
||||
assert_eq!(sp_header.data_len(), 0);
|
||||
}
|
||||
}
|
@ -1,476 +0,0 @@
|
||||
use crate::ecss::PusVersion;
|
||||
use crate::CCSDS_HEADER_LEN;
|
||||
use core::mem::size_of;
|
||||
|
||||
type CrcType = u16;
|
||||
|
||||
/// PUS C secondary header length is fixed
|
||||
pub const PUC_TC_SECONDARY_HEADER_LEN: usize = size_of::<zc::PusTcDataFieldHeader>();
|
||||
pub const PUS_TC_MIN_LEN_WITHOUT_APP_DATA: usize =
|
||||
CCSDS_HEADER_LEN + PUC_TC_SECONDARY_HEADER_LEN + size_of::<CrcType>();
|
||||
const PUS_VERSION: PusVersion = PusVersion::PusC;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
enum AckOpts {
|
||||
Acceptance = 0b1000,
|
||||
Start = 0b0100,
|
||||
Progress = 0b0010,
|
||||
Completion = 0b0001,
|
||||
}
|
||||
|
||||
pub const ACK_ALL: u8 = AckOpts::Acceptance as u8
|
||||
| AckOpts::Start as u8
|
||||
| AckOpts::Progress as u8
|
||||
| AckOpts::Completion as u8;
|
||||
|
||||
pub trait PusTcSecondaryHeader {
|
||||
fn ack_flags(&self) -> u8;
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn source_id(&self) -> u16;
|
||||
}
|
||||
|
||||
pub mod zc {
|
||||
use crate::ecss::{PusError, PusVersion};
|
||||
use crate::tc::{ser, PusTcSecondaryHeader};
|
||||
use zerocopy::{AsBytes, FromBytes, NetworkEndian, Unaligned, U16};
|
||||
|
||||
#[derive(FromBytes, AsBytes, Unaligned)]
|
||||
#[repr(C)]
|
||||
pub struct PusTcDataFieldHeader {
|
||||
version_ack: u8,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
source_id: U16<NetworkEndian>,
|
||||
}
|
||||
|
||||
impl TryFrom<ser::PusTcDataFieldHeader> for PusTcDataFieldHeader {
|
||||
type Error = PusError;
|
||||
fn try_from(value: ser::PusTcDataFieldHeader) -> Result<Self, Self::Error> {
|
||||
if value.version != PusVersion::PusC {
|
||||
return Err(PusError::VersionNotSupported(value.version));
|
||||
}
|
||||
Ok(PusTcDataFieldHeader {
|
||||
version_ack: ((value.version as u8) << 4) | value.ack,
|
||||
service: value.service,
|
||||
subservice: value.subservice,
|
||||
source_id: U16::from(value.source_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PusTcSecondaryHeader for PusTcDataFieldHeader {
|
||||
fn ack_flags(&self) -> u8 {
|
||||
self.version_ack & 0b1111
|
||||
}
|
||||
|
||||
fn service(&self) -> u8 {
|
||||
self.service
|
||||
}
|
||||
|
||||
fn subservice(&self) -> u8 {
|
||||
self.subservice
|
||||
}
|
||||
|
||||
fn source_id(&self) -> u16 {
|
||||
self.source_id.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl PusTcDataFieldHeader {
|
||||
pub fn to_bytes(&self, slice: &mut (impl AsMut<[u8]> + ?Sized)) -> Option<()> {
|
||||
self.write_to(slice.as_mut())
|
||||
}
|
||||
|
||||
pub fn from_bytes(slice: &(impl AsRef<[u8]> + ?Sized)) -> Option<Self> {
|
||||
Self::read_from(slice.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod ser {
|
||||
use crate::ecss::{PusError, PusPacket, PusVersion, CRC_CCITT_FALSE};
|
||||
use crate::ser::SpHeader;
|
||||
use crate::tc::{
|
||||
PusTcSecondaryHeader, ACK_ALL, PUC_TC_SECONDARY_HEADER_LEN,
|
||||
PUS_TC_MIN_LEN_WITHOUT_APP_DATA, PUS_VERSION,
|
||||
};
|
||||
use crate::{zc, CcsdsPacket, PacketError, PacketId, PacketSequenceCtrl, PacketType};
|
||||
use alloc::vec::Vec;
|
||||
use core::mem::size_of;
|
||||
use delegate::delegate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct PusTcDataFieldHeader {
|
||||
pub service: u8,
|
||||
pub subservice: u8,
|
||||
pub source_id: u16,
|
||||
pub ack: u8,
|
||||
pub version: PusVersion,
|
||||
}
|
||||
|
||||
impl PusTcSecondaryHeader for PusTcDataFieldHeader {
|
||||
fn ack_flags(&self) -> u8 {
|
||||
self.ack
|
||||
}
|
||||
|
||||
fn service(&self) -> u8 {
|
||||
self.service
|
||||
}
|
||||
|
||||
fn subservice(&self) -> u8 {
|
||||
self.subservice
|
||||
}
|
||||
|
||||
fn source_id(&self) -> u16 {
|
||||
self.source_id
|
||||
}
|
||||
}
|
||||
impl TryFrom<super::zc::PusTcDataFieldHeader> for PusTcDataFieldHeader {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: super::zc::PusTcDataFieldHeader) -> Result<Self, Self::Error> {
|
||||
Ok(PusTcDataFieldHeader {
|
||||
service: value.service(),
|
||||
subservice: value.subservice(),
|
||||
source_id: value.source_id(),
|
||||
ack: value.ack_flags(),
|
||||
version: PUS_VERSION,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PusTcDataFieldHeader {
|
||||
pub fn new(service: u8, subservice: u8, ack: u8) -> Self {
|
||||
PusTcDataFieldHeader {
|
||||
service,
|
||||
subservice,
|
||||
ack: ack & 0b1111,
|
||||
source_id: 0,
|
||||
version: PusVersion::PusC,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct PusTc<'slice> {
|
||||
pub sph: SpHeader,
|
||||
pub data_field_header: PusTcDataFieldHeader,
|
||||
#[serde(skip)]
|
||||
raw_data: Option<&'slice [u8]>,
|
||||
app_data: Option<&'slice [u8]>,
|
||||
crc16: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'slice> PusTc<'slice> {
|
||||
pub fn new(
|
||||
sph: &mut SpHeader,
|
||||
service: u8,
|
||||
subservice: u8,
|
||||
app_data: Option<&'slice [u8]>,
|
||||
) -> Self {
|
||||
sph.packet_id.ptype = PacketType::Tc;
|
||||
PusTc {
|
||||
sph: *sph,
|
||||
raw_data: None,
|
||||
app_data,
|
||||
data_field_header: PusTcDataFieldHeader::new(service, subservice, ACK_ALL),
|
||||
crc16: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len_packed(&self) -> usize {
|
||||
let mut length = super::PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||
if let Some(app_data) = self.app_data {
|
||||
length += app_data.len();
|
||||
}
|
||||
length
|
||||
}
|
||||
|
||||
/// Calculate the CCSDS space packet data length field and sets it
|
||||
pub fn set_ccsds_data_len(&mut self) {
|
||||
self.sph.data_len =
|
||||
self.len_packed() as u16 - size_of::<crate::zc::SpHeader>() as u16 - 1;
|
||||
}
|
||||
|
||||
fn crc_from_raw_data(&self) -> Result<u16, PusError> {
|
||||
if let Some(raw_data) = self.raw_data {
|
||||
if raw_data.len() < 2 {
|
||||
return Err(PusError::RawDataTooShort(raw_data.len()));
|
||||
}
|
||||
return Ok(u16::from_be_bytes(
|
||||
raw_data[raw_data.len() - 2..raw_data.len()]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
}
|
||||
Err(PusError::NoRawData)
|
||||
}
|
||||
|
||||
pub fn calc_crc16(&mut self) {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sph);
|
||||
digest.update(sph_zc.as_bytes());
|
||||
let pus_tc_header =
|
||||
super::zc::PusTcDataFieldHeader::try_from(self.data_field_header).unwrap();
|
||||
digest.update(pus_tc_header.as_bytes());
|
||||
if let Some(app_data) = self.app_data {
|
||||
digest.update(app_data);
|
||||
}
|
||||
self.crc16 = Some(digest.finalize())
|
||||
}
|
||||
|
||||
/// This function updates two important internal fields: The CCSDS packet length in the
|
||||
/// space packet header and the CRC16 field. This function should be called before
|
||||
/// the TC packet is serialized
|
||||
pub fn update_packet_fields(&mut self) {
|
||||
self.set_ccsds_data_len();
|
||||
self.calc_crc16();
|
||||
}
|
||||
|
||||
pub fn copy_to_buf(
|
||||
&self,
|
||||
slice: &mut (impl AsMut<[u8]> + ?Sized),
|
||||
) -> Result<usize, PusError> {
|
||||
if self.crc16.is_none() {
|
||||
return Err(PusError::CrcCalculationMissing);
|
||||
}
|
||||
let mut_slice = slice.as_mut();
|
||||
let mut curr_idx = 0;
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sph);
|
||||
let tc_header_len = size_of::<super::zc::PusTcDataFieldHeader>();
|
||||
let mut total_size = super::PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||
if let Some(app_data) = self.app_data {
|
||||
total_size += app_data.len();
|
||||
};
|
||||
if total_size > mut_slice.len() {
|
||||
return Err(PusError::OtherPacketError(
|
||||
PacketError::ToBytesSliceTooSmall(total_size),
|
||||
));
|
||||
}
|
||||
sph_zc
|
||||
.to_bytes(&mut mut_slice[curr_idx..curr_idx + 6])
|
||||
.ok_or(PusError::OtherPacketError(
|
||||
PacketError::ToBytesZeroCopyError,
|
||||
))?;
|
||||
curr_idx += 6;
|
||||
// The PUS version is hardcoded to PUS C
|
||||
let pus_tc_header =
|
||||
super::zc::PusTcDataFieldHeader::try_from(self.data_field_header).unwrap();
|
||||
|
||||
pus_tc_header
|
||||
.to_bytes(&mut mut_slice[curr_idx..curr_idx + tc_header_len])
|
||||
.ok_or(PusError::OtherPacketError(
|
||||
PacketError::ToBytesZeroCopyError,
|
||||
))?;
|
||||
curr_idx += tc_header_len;
|
||||
if let Some(app_data) = self.app_data {
|
||||
mut_slice[curr_idx..curr_idx + app_data.len()].copy_from_slice(app_data);
|
||||
curr_idx += app_data.len();
|
||||
}
|
||||
mut_slice[curr_idx..curr_idx + 2]
|
||||
.copy_from_slice(self.crc16.unwrap().to_be_bytes().as_slice());
|
||||
curr_idx += 2;
|
||||
Ok(curr_idx)
|
||||
}
|
||||
|
||||
pub fn append_to_vec(&self, vec: &mut Vec<u8>) -> Result<usize, PusError> {
|
||||
if self.crc16.is_none() {
|
||||
return Err(PusError::CrcCalculationMissing);
|
||||
}
|
||||
let sph_zc = crate::zc::SpHeader::from(self.sph);
|
||||
let mut appended_len = super::PUS_TC_MIN_LEN_WITHOUT_APP_DATA;
|
||||
if let Some(app_data) = self.app_data {
|
||||
appended_len += app_data.len();
|
||||
};
|
||||
vec.extend_from_slice(sph_zc.as_bytes());
|
||||
// The PUS version is hardcoded to PUS C
|
||||
let pus_tc_header =
|
||||
super::zc::PusTcDataFieldHeader::try_from(self.data_field_header).unwrap();
|
||||
vec.extend_from_slice(pus_tc_header.as_bytes());
|
||||
if let Some(app_data) = self.app_data {
|
||||
vec.extend_from_slice(app_data);
|
||||
}
|
||||
vec.extend_from_slice(self.crc16.unwrap().to_be_bytes().as_slice());
|
||||
Ok(appended_len)
|
||||
}
|
||||
|
||||
pub fn new_from_raw_slice(
|
||||
slice: &'slice (impl AsRef<[u8]> + ?Sized),
|
||||
) -> Result<(Self, usize), PusError> {
|
||||
let slice_ref = slice.as_ref();
|
||||
let raw_data_len = slice_ref.len();
|
||||
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let mut current_idx = 0;
|
||||
let sph = zc::SpHeader::from_bytes(&slice_ref[current_idx..current_idx + 6]).ok_or(
|
||||
PusError::OtherPacketError(PacketError::FromBytesZeroCopyError),
|
||||
)?;
|
||||
current_idx += 6;
|
||||
let total_len = sph.total_len();
|
||||
if raw_data_len < total_len || total_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len));
|
||||
}
|
||||
let sec_header = crate::tc::zc::PusTcDataFieldHeader::from_bytes(
|
||||
&slice_ref[current_idx..current_idx + PUC_TC_SECONDARY_HEADER_LEN],
|
||||
)
|
||||
.ok_or(PusError::OtherPacketError(
|
||||
PacketError::FromBytesZeroCopyError,
|
||||
))?;
|
||||
current_idx += PUC_TC_SECONDARY_HEADER_LEN;
|
||||
let mut pus_tc = PusTc {
|
||||
sph: SpHeader::from(sph),
|
||||
data_field_header: PusTcDataFieldHeader::try_from(sec_header).unwrap(),
|
||||
raw_data: Some(slice_ref),
|
||||
app_data: match current_idx {
|
||||
_ if current_idx == total_len - 2 => None,
|
||||
_ if current_idx > total_len - 2 => {
|
||||
return Err(PusError::RawDataTooShort(raw_data_len))
|
||||
}
|
||||
_ => Some(&slice_ref[current_idx..total_len - 2]),
|
||||
},
|
||||
crc16: None,
|
||||
};
|
||||
pus_tc.crc_from_raw_data()?;
|
||||
pus_tc.verify()?;
|
||||
Ok((pus_tc, total_len))
|
||||
}
|
||||
|
||||
fn verify(&mut self) -> Result<(), PusError> {
|
||||
let mut digest = CRC_CCITT_FALSE.digest();
|
||||
if self.raw_data.is_none() {
|
||||
return Err(PusError::NoRawData);
|
||||
}
|
||||
let raw_data = self.raw_data.unwrap();
|
||||
digest.update(raw_data.as_ref());
|
||||
if digest.finalize() == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let crc16 = self.crc_from_raw_data()?;
|
||||
Err(PusError::IncorrectCrc(crc16))
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl CcsdsPacket for PusTc<'_> {
|
||||
delegate!(to self.sph {
|
||||
fn ccsds_version(&self) -> u8;
|
||||
fn packet_id(&self) -> PacketId;
|
||||
fn psc(&self) -> PacketSequenceCtrl;
|
||||
fn data_len(&self) -> u16;
|
||||
});
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl PusPacket for PusTc<'_> {
|
||||
delegate!(to self.data_field_header {
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
});
|
||||
|
||||
fn user_data(&self) -> Option<&[u8]> {
|
||||
self.app_data
|
||||
}
|
||||
|
||||
fn crc16(&self) -> Option<u16> {
|
||||
self.crc16
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection RsTraitImplementation
|
||||
impl PusTcSecondaryHeader for PusTc<'_> {
|
||||
delegate!(to self.data_field_header {
|
||||
fn service(&self) -> u8;
|
||||
fn subservice(&self) -> u8;
|
||||
fn source_id(&self) -> u16;
|
||||
fn ack_flags(&self) -> u8;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ecss::PusPacket;
|
||||
use crate::ser::SpHeader;
|
||||
use crate::tc::ser::PusTc;
|
||||
use crate::tc::PusTcSecondaryHeader;
|
||||
use crate::tc::ACK_ALL;
|
||||
use crate::{CcsdsPacket, PacketType};
|
||||
use alloc::vec::Vec;
|
||||
use postcard::to_stdvec;
|
||||
|
||||
#[test]
|
||||
fn test_tc() {
|
||||
let mut sph = SpHeader::tc(0x01, 0, 0).unwrap();
|
||||
let mut pus_tc = PusTc::new(&mut sph, 17, 1, None);
|
||||
let _out = to_stdvec(&pus_tc).unwrap();
|
||||
assert_eq!(pus_tc.crc16(), None);
|
||||
let mut test_buf: [u8; 32] = [0; 32];
|
||||
pus_tc.update_packet_fields();
|
||||
verify_test_tc(&pus_tc);
|
||||
assert_eq!(pus_tc.len_packed(), 13);
|
||||
let size = pus_tc
|
||||
.copy_to_buf(test_buf.as_mut_slice())
|
||||
.expect("Error writing TC to buffer");
|
||||
assert_eq!(size, 13);
|
||||
let (tc_from_raw, mut size) = PusTc::new_from_raw_slice(&test_buf)
|
||||
.expect("Creating PUS TC struct from raw buffer failed");
|
||||
assert_eq!(size, 13);
|
||||
verify_test_tc(&tc_from_raw);
|
||||
verify_test_tc_raw(PacketType::Tm, &test_buf);
|
||||
|
||||
let mut test_vec = Vec::new();
|
||||
size = pus_tc
|
||||
.append_to_vec(&mut test_vec)
|
||||
.expect("Error writing TC to vector");
|
||||
assert_eq!(size, 13);
|
||||
assert_eq!(&test_buf[0..pus_tc.len_packed()], test_vec.as_slice());
|
||||
verify_test_tc_raw(PacketType::Tm, &test_vec.as_slice());
|
||||
}
|
||||
|
||||
fn verify_test_tc(tc: &PusTc) {
|
||||
assert_eq!(PusPacket::service(tc), 17);
|
||||
assert_eq!(PusPacket::subservice(tc), 1);
|
||||
assert_eq!(tc.user_data(), None);
|
||||
assert_eq!(tc.source_id(), 0);
|
||||
assert_eq!(tc.apid(), 0x01);
|
||||
assert_eq!(tc.ack_flags(), ACK_ALL);
|
||||
assert_eq!(tc.sph, SpHeader::tc(0x01, 0, 6).unwrap());
|
||||
}
|
||||
|
||||
fn verify_test_tc_raw(ptype: PacketType, slice: &impl AsRef<[u8]>) {
|
||||
// Reference comparison implementation:
|
||||
// https://github.com/robamu-org/py-spacepackets/blob/main/examples/example_pus.py
|
||||
let slice = slice.as_ref();
|
||||
// 0x1801 is the generic
|
||||
if ptype == PacketType::Tm {
|
||||
assert_eq!(slice[0], 0x18);
|
||||
} else {
|
||||
assert_eq!(slice[0], 0x08);
|
||||
}
|
||||
// APID is 0x01
|
||||
assert_eq!(slice[1], 0x01);
|
||||
// Unsegmented packets
|
||||
assert_eq!(slice[2], 0xc0);
|
||||
// Sequence count 0
|
||||
assert_eq!(slice[3], 0x00);
|
||||
assert_eq!(slice[4], 0x00);
|
||||
// Space data length of 6 equals total packet length of 13
|
||||
assert_eq!(slice[5], 0x06);
|
||||
// PUS Version C 0b0010 and ACK flags 0b1111
|
||||
assert_eq!(slice[6], 0x2f);
|
||||
// Service 17
|
||||
assert_eq!(slice[7], 0x11);
|
||||
// Subservice 1
|
||||
assert_eq!(slice[8], 0x01);
|
||||
// Source ID 0
|
||||
assert_eq!(slice[9], 0x00);
|
||||
assert_eq!(slice[10], 0x00);
|
||||
// CRC first byte assuming big endian format is 0x16 and 0x1d
|
||||
assert_eq!(slice[11], 0x16);
|
||||
assert_eq!(slice[12], 0x1d);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
Loading…
Reference in New Issue
Block a user