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