Skip to main content

leodos_protocols/misc/sle/
raf.rs

1//! RAF (Return All Frames) service PDUs.
2//!
3//! RAF is the SLE service for receiving downlink telemetry frames
4//! from a ground station. The client binds to a service instance,
5//! starts data transfer, and receives TM frames wrapped in
6//! TransferData invocations.
7
8use super::ber::{self, BerReader, BerWriter, Class, tags};
9use super::isp1::Credentials;
10use super::types::{BindResult, ServiceType, SleError, Time};
11
12/// Maximum length of an initiator/responder identifier string.
13const MAX_ID_LEN: usize = 64;
14
15/// Requested frame quality for RAF Start.
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17#[repr(u8)]
18pub enum RequestedFrameQuality {
19    /// Only good (error-free) frames.
20    GoodOnly = 0,
21    /// Only erred frames.
22    ErredOnly = 1,
23    /// All frames regardless of quality.
24    AllFrames = 2,
25}
26
27impl RequestedFrameQuality {
28    /// Converts from an integer value.
29    pub fn from_i64(v: i64) -> Result<Self, SleError> {
30        match v {
31            0 => Ok(Self::GoodOnly),
32            1 => Ok(Self::ErredOnly),
33            2 => Ok(Self::AllFrames),
34            _ => Err(SleError::InvalidEnumValue),
35        }
36    }
37}
38
39/// RAF operation identifier tags (context-specific).
40/// These are the top-level CHOICE tags in the RAF PDU.
41#[derive(Copy, Clone, Debug, PartialEq, Eq)]
42#[repr(u8)]
43pub enum RafOp {
44    /// Bind invocation from user to provider.
45    Bind = 0,
46    /// Bind return from provider to user.
47    BindReturn = 1,
48    /// Unbind invocation.
49    Unbind = 2,
50    /// Unbind return.
51    UnbindReturn = 3,
52    /// Start invocation.
53    Start = 4,
54    /// Start return.
55    StartReturn = 5,
56    /// Stop invocation.
57    Stop = 6,
58    /// Stop return.
59    StopReturn = 7,
60    /// Transfer buffer (contains data invocations).
61    TransferBuffer = 8,
62    /// Status report.
63    StatusReport = 9,
64}
65
66/// RAF Bind invocation PDU.
67#[derive(Clone, Debug)]
68pub struct RafBindInvocation {
69    /// Identifier of the initiator (client).
70    pub initiator_id: [u8; MAX_ID_LEN],
71    /// Length of initiator_id.
72    pub initiator_id_len: usize,
73    /// Identifier of the responder (ground station).
74    pub responder_id: [u8; MAX_ID_LEN],
75    /// Length of responder_id.
76    pub responder_id_len: usize,
77    /// Type of RAF service requested.
78    pub service_type: ServiceType,
79    /// Protocol version number.
80    pub version: u16,
81    /// Optional authentication credentials.
82    pub credentials: Option<Credentials>,
83}
84
85impl RafBindInvocation {
86    /// Creates a new bind invocation.
87    pub fn new(
88        initiator_id: &[u8],
89        responder_id: &[u8],
90        service_type: ServiceType,
91        version: u16,
92        credentials: Option<Credentials>,
93    ) -> Result<Self, SleError> {
94        if initiator_id.len() > MAX_ID_LEN
95            || responder_id.len() > MAX_ID_LEN
96        {
97            return Err(SleError::TooLong);
98        }
99        let mut init = [0u8; MAX_ID_LEN];
100        init[..initiator_id.len()]
101            .copy_from_slice(initiator_id);
102        let mut resp = [0u8; MAX_ID_LEN];
103        resp[..responder_id.len()]
104            .copy_from_slice(responder_id);
105        Ok(Self {
106            initiator_id: init,
107            initiator_id_len: initiator_id.len(),
108            responder_id: resp,
109            responder_id_len: responder_id.len(),
110            service_type,
111            version,
112            credentials,
113        })
114    }
115
116    /// Returns the initiator identifier as a byte slice.
117    pub fn initiator_id(&self) -> &[u8] {
118        &self.initiator_id[..self.initiator_id_len]
119    }
120
121    /// Returns the responder identifier as a byte slice.
122    pub fn responder_id(&self) -> &[u8] {
123        &self.responder_id[..self.responder_id_len]
124    }
125
126    /// Encodes this PDU into `buf` using BER.
127    /// Returns the number of bytes written.
128    pub fn encode(
129        &self,
130        buf: &mut [u8],
131    ) -> Result<usize, SleError> {
132        let mut w = BerWriter::new(buf);
133        let outer = w.begin_context(
134            RafOp::Bind as u8,
135            true,
136        )?;
137        let seq = w.begin_sequence()?;
138
139        // credentials (CHOICE: NULL or SEQUENCE)
140        match &self.credentials {
141            None => w.write_null()?,
142            Some(cred) => {
143                let cred_seq = w.begin_sequence()?;
144                w.write_octet_string(&cred.time)?;
145                w.write_integer(cred.random as i64)?;
146                w.write_octet_string(&cred.hash)?;
147                w.end_sequence(cred_seq)?;
148            }
149        }
150
151        w.write_octet_string(self.initiator_id())?;
152        w.write_octet_string(self.responder_id())?;
153        w.write_enum(self.service_type as i64)?;
154        w.write_integer(self.version as i64)?;
155
156        w.end_sequence(seq)?;
157        w.end_sequence(outer)?;
158        Ok(w.len())
159    }
160
161    /// Decodes a Bind invocation from BER bytes.
162    pub fn decode(buf: &[u8]) -> Result<Self, SleError> {
163        let mut r = BerReader::new(buf);
164
165        // context tag [0] constructed
166        let (tag, _len) = r.read_context_tag()?;
167        if tag != RafOp::Bind as u8 {
168            return Err(SleError::UnexpectedTag);
169        }
170
171        let _seq_len = r.read_sequence()?;
172
173        // credentials
174        let (peek_tag, peek_class, _) = r.peek_tag()?;
175        let credentials = if peek_tag == tags::NULL
176            && peek_class == Class::Universal
177        {
178            r.read_null()?;
179            None
180        } else {
181            let _cred_len = r.read_sequence()?;
182            let time_bytes = r.read_octet_string()?;
183            let random = r.read_integer()? as u32;
184            let hash_bytes = r.read_octet_string()?;
185            let mut time = [0u8; 8];
186            if time_bytes.len() != 8 {
187                return Err(SleError::Truncated);
188            }
189            time.copy_from_slice(time_bytes);
190            let mut hash = [0u8; 20];
191            if hash_bytes.len() != 20 {
192                return Err(SleError::Truncated);
193            }
194            hash.copy_from_slice(hash_bytes);
195            Some(Credentials { time, random, hash })
196        };
197
198        let initiator = r.read_octet_string()?;
199        let responder = r.read_octet_string()?;
200        let service_type =
201            ServiceType::from_u8(r.read_enum()? as u8)?;
202        let version = r.read_integer()? as u16;
203
204        Self::new(
205            initiator,
206            responder,
207            service_type,
208            version,
209            credentials,
210        )
211    }
212}
213
214/// RAF Bind return PDU.
215#[derive(Copy, Clone, Debug, PartialEq, Eq)]
216pub struct RafBindReturn {
217    /// Result of the bind operation.
218    pub result: BindResult,
219    /// Responder credentials (if authenticating).
220    pub credentials: Option<Credentials>,
221}
222
223impl RafBindReturn {
224    /// Encodes this PDU into `buf`.
225    pub fn encode(
226        &self,
227        buf: &mut [u8],
228    ) -> Result<usize, SleError> {
229        let mut w = BerWriter::new(buf);
230        let outer = w.begin_context(
231            RafOp::BindReturn as u8,
232            true,
233        )?;
234        let seq = w.begin_sequence()?;
235
236        match &self.credentials {
237            None => w.write_null()?,
238            Some(cred) => {
239                let cred_seq = w.begin_sequence()?;
240                w.write_octet_string(&cred.time)?;
241                w.write_integer(cred.random as i64)?;
242                w.write_octet_string(&cred.hash)?;
243                w.end_sequence(cred_seq)?;
244            }
245        }
246
247        w.write_enum(self.result as i64)?;
248
249        w.end_sequence(seq)?;
250        w.end_sequence(outer)?;
251        Ok(w.len())
252    }
253
254    /// Decodes a Bind return from BER bytes.
255    pub fn decode(buf: &[u8]) -> Result<Self, SleError> {
256        let mut r = BerReader::new(buf);
257
258        let (tag, _len) = r.read_context_tag()?;
259        if tag != RafOp::BindReturn as u8 {
260            return Err(SleError::UnexpectedTag);
261        }
262
263        let _seq_len = r.read_sequence()?;
264
265        let (peek_tag, peek_class, _) = r.peek_tag()?;
266        let credentials = if peek_tag == tags::NULL
267            && peek_class == Class::Universal
268        {
269            r.read_null()?;
270            None
271        } else {
272            let _cred_len = r.read_sequence()?;
273            let time_bytes = r.read_octet_string()?;
274            let random = r.read_integer()? as u32;
275            let hash_bytes = r.read_octet_string()?;
276            let mut time = [0u8; 8];
277            time.copy_from_slice(
278                time_bytes.get(..8).ok_or(SleError::Truncated)?,
279            );
280            let mut hash = [0u8; 20];
281            hash.copy_from_slice(
282                hash_bytes
283                    .get(..20)
284                    .ok_or(SleError::Truncated)?,
285            );
286            Some(Credentials { time, random, hash })
287        };
288
289        let result =
290            BindResult::from_u8(r.read_enum()? as u8)?;
291
292        Ok(Self {
293            result,
294            credentials,
295        })
296    }
297}
298
299/// RAF Start invocation PDU.
300#[derive(Copy, Clone, Debug, PartialEq, Eq)]
301pub struct RafStartInvocation {
302    /// Start time — None means "start from now".
303    pub start_time: Option<Time>,
304    /// Stop time — None means "until stopped".
305    pub stop_time: Option<Time>,
306    /// Requested frame quality filter.
307    pub quality: RequestedFrameQuality,
308    /// Optional authentication credentials.
309    pub credentials: Option<Credentials>,
310}
311
312impl RafStartInvocation {
313    /// Encodes this PDU into `buf`.
314    pub fn encode(
315        &self,
316        buf: &mut [u8],
317    ) -> Result<usize, SleError> {
318        let mut w = BerWriter::new(buf);
319        let outer = w.begin_context(
320            RafOp::Start as u8,
321            true,
322        )?;
323        let seq = w.begin_sequence()?;
324
325        match &self.credentials {
326            None => w.write_null()?,
327            Some(cred) => {
328                let cred_seq = w.begin_sequence()?;
329                w.write_octet_string(&cred.time)?;
330                w.write_integer(cred.random as i64)?;
331                w.write_octet_string(&cred.hash)?;
332                w.end_sequence(cred_seq)?;
333            }
334        }
335
336        // start time: context [0] or NULL
337        match &self.start_time {
338            None => w.write_null()?,
339            Some(t) => w.write_octet_string(&t.cds)?,
340        }
341
342        // stop time: context [1] or NULL
343        match &self.stop_time {
344            None => w.write_null()?,
345            Some(t) => w.write_octet_string(&t.cds)?,
346        }
347
348        w.write_enum(self.quality as i64)?;
349
350        w.end_sequence(seq)?;
351        w.end_sequence(outer)?;
352        Ok(w.len())
353    }
354
355    /// Decodes a Start invocation from BER bytes.
356    pub fn decode(buf: &[u8]) -> Result<Self, SleError> {
357        let mut r = BerReader::new(buf);
358
359        let (tag, _len) = r.read_context_tag()?;
360        if tag != RafOp::Start as u8 {
361            return Err(SleError::UnexpectedTag);
362        }
363
364        let _seq_len = r.read_sequence()?;
365
366        // credentials
367        let (peek_tag, peek_class, _) = r.peek_tag()?;
368        let credentials = if peek_tag == tags::NULL
369            && peek_class == Class::Universal
370        {
371            r.read_null()?;
372            None
373        } else {
374            let _cred_len = r.read_sequence()?;
375            let time_bytes = r.read_octet_string()?;
376            let random = r.read_integer()? as u32;
377            let hash_bytes = r.read_octet_string()?;
378            let mut time = [0u8; 8];
379            time.copy_from_slice(
380                time_bytes.get(..8).ok_or(SleError::Truncated)?,
381            );
382            let mut hash = [0u8; 20];
383            hash.copy_from_slice(
384                hash_bytes
385                    .get(..20)
386                    .ok_or(SleError::Truncated)?,
387            );
388            Some(Credentials { time, random, hash })
389        };
390
391        // start_time
392        let (pt, _, _) = r.peek_tag()?;
393        let start_time = if pt == tags::NULL {
394            r.read_null()?;
395            None
396        } else {
397            let bytes = r.read_octet_string()?;
398            let (t, _) = Time::decode(bytes)?;
399            Some(t)
400        };
401
402        // stop_time
403        let (pt, _, _) = r.peek_tag()?;
404        let stop_time = if pt == tags::NULL {
405            r.read_null()?;
406            None
407        } else {
408            let bytes = r.read_octet_string()?;
409            let (t, _) = Time::decode(bytes)?;
410            Some(t)
411        };
412
413        let quality =
414            RequestedFrameQuality::from_i64(r.read_enum()?)?;
415
416        Ok(Self {
417            start_time,
418            stop_time,
419            quality,
420            credentials,
421        })
422    }
423}
424
425/// A single TM frame delivery within a RAF transfer buffer.
426#[derive(Clone, Debug)]
427pub struct RafTransferDataInvocation {
428    /// Earth receive time of the frame.
429    pub earth_receive_time: Time,
430    /// Data link continuity indicator (-1 if unknown).
431    pub data_link_continuity: i16,
432    /// The raw TM frame data. Stored inline, max 2048 bytes.
433    frame_buf: [u8; Self::MAX_FRAME_LEN],
434    /// Actual frame length.
435    frame_len: usize,
436}
437
438impl RafTransferDataInvocation {
439    /// Maximum supported frame size.
440    pub const MAX_FRAME_LEN: usize = 2048;
441
442    /// Creates a new transfer data invocation.
443    pub fn new(
444        earth_receive_time: Time,
445        data_link_continuity: i16,
446        frame: &[u8],
447    ) -> Result<Self, SleError> {
448        if frame.len() > Self::MAX_FRAME_LEN {
449            return Err(SleError::TooLong);
450        }
451        let mut frame_buf = [0u8; Self::MAX_FRAME_LEN];
452        frame_buf[..frame.len()].copy_from_slice(frame);
453        Ok(Self {
454            earth_receive_time,
455            data_link_continuity,
456            frame_buf,
457            frame_len: frame.len(),
458        })
459    }
460
461    /// Returns the TM frame data.
462    pub fn frame(&self) -> &[u8] {
463        &self.frame_buf[..self.frame_len]
464    }
465
466    /// Encodes this invocation into `buf`.
467    pub fn encode(
468        &self,
469        buf: &mut [u8],
470    ) -> Result<usize, SleError> {
471        let mut w = BerWriter::new(buf);
472        let seq = w.begin_sequence()?;
473        w.write_octet_string(&self.earth_receive_time.cds)?;
474        w.write_integer(
475            self.data_link_continuity as i64,
476        )?;
477        w.write_octet_string(self.frame())?;
478        w.end_sequence(seq)?;
479        Ok(w.len())
480    }
481
482    /// Decodes a transfer data invocation from BER bytes.
483    pub fn decode(buf: &[u8]) -> Result<Self, SleError> {
484        let mut r = BerReader::new(buf);
485        let _seq_len = r.read_sequence()?;
486
487        let time_bytes = r.read_octet_string()?;
488        let (ert, _) = Time::decode(time_bytes)?;
489        let continuity = r.read_integer()? as i16;
490        let frame_data = r.read_octet_string()?;
491
492        Self::new(ert, continuity, frame_data)
493    }
494}
495
496/// A RAF transfer buffer containing one or more data invocations.
497///
498/// On the wire this is a SEQUENCE OF RafTransferDataInvocation.
499/// We provide encode/decode for individual items; the caller
500/// iterates the buffer.
501pub struct RafTransferBuffer;
502
503impl RafTransferBuffer {
504    /// Decodes the outer SEQUENCE header and returns a sub-reader
505    /// positioned at the first element. The caller should
506    /// repeatedly call `RafTransferDataInvocation::decode` on
507    /// sub-slices until the content is exhausted.
508    pub fn decode_header(
509        buf: &[u8],
510    ) -> Result<(usize, usize), SleError> {
511        let mut r = BerReader::new(buf);
512        // context tag for TransferBuffer
513        let (tag, _class, _, _consumed) =
514            ber::decode_tag(&buf[r.pos()..])?;
515        if tag != RafOp::TransferBuffer as u8 {
516            return Err(SleError::UnexpectedTag);
517        }
518        r.read_tag()?;
519        let content_len = r.read_length()?;
520        Ok((content_len, r.pos()))
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    #[test]
529    fn bind_invocation_roundtrip() {
530        let bind = RafBindInvocation::new(
531            b"user1",
532            b"gs-station",
533            ServiceType::RafOnline,
534            5,
535            None,
536        )
537        .unwrap();
538
539        let mut buf = [0u8; 256];
540        let n = bind.encode(&mut buf).unwrap();
541
542        let decoded =
543            RafBindInvocation::decode(&buf[..n]).unwrap();
544        assert_eq!(decoded.initiator_id(), b"user1");
545        assert_eq!(decoded.responder_id(), b"gs-station");
546        assert_eq!(
547            decoded.service_type,
548            ServiceType::RafOnline,
549        );
550        assert_eq!(decoded.version, 5);
551        assert!(decoded.credentials.is_none());
552    }
553
554    #[test]
555    fn bind_return_roundtrip() {
556        let ret = RafBindReturn {
557            result: BindResult::Success,
558            credentials: None,
559        };
560        let mut buf = [0u8; 64];
561        let n = ret.encode(&mut buf).unwrap();
562
563        let decoded =
564            RafBindReturn::decode(&buf[..n]).unwrap();
565        assert_eq!(decoded.result, BindResult::Success);
566        assert!(decoded.credentials.is_none());
567    }
568
569    #[test]
570    fn start_invocation_roundtrip() {
571        let start = RafStartInvocation {
572            start_time: None,
573            stop_time: None,
574            quality: RequestedFrameQuality::AllFrames,
575            credentials: None,
576        };
577        let mut buf = [0u8; 128];
578        let n = start.encode(&mut buf).unwrap();
579
580        let decoded =
581            RafStartInvocation::decode(&buf[..n]).unwrap();
582        assert!(decoded.start_time.is_none());
583        assert!(decoded.stop_time.is_none());
584        assert_eq!(
585            decoded.quality,
586            RequestedFrameQuality::AllFrames,
587        );
588    }
589
590    #[test]
591    fn transfer_data_roundtrip() {
592        let frame_data = [0xDE, 0xAD, 0xBE, 0xEF];
593        let td = RafTransferDataInvocation::new(
594            Time::from_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
595            -1,
596            &frame_data,
597        )
598        .unwrap();
599
600        let mut buf = [0u8; 128];
601        let n = td.encode(&mut buf).unwrap();
602
603        let decoded =
604            RafTransferDataInvocation::decode(&buf[..n])
605                .unwrap();
606        assert_eq!(decoded.frame(), &frame_data);
607        assert_eq!(decoded.data_link_continuity, -1);
608        assert_eq!(
609            decoded.earth_receive_time,
610            Time::from_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
611        );
612    }
613}