rstango 0.4.0

Implementation of the Tango distributed control system
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
//! The values of an attribute, in the structures every version of the IDL has for them.

use std::time::SystemTime;
use std::mem;

use crate::value::{AttrDataFormat, AttrDataKind, DevEncoded, DevEnum, Scalar, TangoElement, Value, ValueFormat};
use crate::error::{reason, Error, ErrorKind, Result};
use crate::dev::{AttrQuality, AttrRead, AttrReady, DevState, Named, NamedResult};

use super::super::orb::cdr::{CdrReader, CdrWriter};
use super::super::DevVersion;
use super::{
    attr_data_kind, attr_data_type, read_attr_data_format, read_encoded, read_spectrum, write_array_tc, write_attr_data_format,
    write_attr_dim, write_encoded, CdrTangoElement,
    ATT_BOOL, ATT_DEVICE_STATE, ATT_DOUBLE, ATT_ENCODED, ATT_FLOAT, ATT_LONG, ATT_LONG64,
    ATT_NO_DATA, ATT_SHORT, ATT_STATE, ATT_STRING, ATT_UCHAR, ATT_ULONG, ATT_ULONG64, ATT_USHORT,
    DATA_TYPE_UNKNOWN, DEV_ENCODED, DEV_ENUM, TK_ALIAS, TK_ENUM, TK_NULL, TK_VOID,
};


/// Common function for all versions of attribute reading.
///
/// The IDL only has four of these structures for the six device versions: v2 reuses the
/// v1 one and v6 the v5 one, so what changes from one version to the next is
/// - v3 gains a separate pair of dimensions for the read and the write values, which
///   tells a read-only attribute from a write-only one, and an error list per attribute;
/// - v4 replaces the CORBA::Any with a discriminated union, which is much easier to
///   decode, and announces the data format instead of leaving it to be guessed;
/// - v5 adds a data_type field, the declared type of the attribute, which the arm carrying
///   the value only tells apart from for a DevEnum, a short arm being the one both of them
///   travel in. It would be nice to check the two against each other at some point, to
///   ensure coherency!
pub fn read_attr(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<NamedResult<AttrRead<Value>>> {
    let (name, _, read) = read_attr_timed(buf, version)?;
    Ok(Named::new(name, read))
}

/// Read an attribute value structure, see [`read_attr`], giving back the time it carries
/// along with it, which a failed reading has no other place for.
pub(super) fn read_attr_timed(buf: &mut CdrReader<'_>, version: DevVersion) -> Result<(Scalar<str>, SystemTime, Result<AttrRead<Value>>)> {

    // The values of both directions, as the single sequence they travel in, which only the
    // dimensions that come after them tell how to split.
    let mut values = if version <= DevVersion::V3 {
        read_attr_values_any(buf)?.map(AttrValues::Flat)
    } else {
        read_attr_values_union(buf)?
    };

    let quality = buf.read::<AttrQuality>()?;
    if quality == AttrQuality::Invalid {
        values = None;
    }

    let mut format = None;
    if version >= DevVersion::V4 {
        format = read_attr_data_format(buf)?;
    }

    if version >= DevVersion::V5 {

        let data_type = buf.read_long()?;

        // A DevEnum travels in the very same short arm a DevShort does, the declared type
        // being the only thing telling the two apart, so the values that were just read are
        // relabelled here, sharing the very same octets. Note that this is only ever
        // possible from v5 on, which is both the version that declares the type along the
        // value and the one DevEnum appeared with.
        if data_type == DEV_ENUM
        && let Some(AttrValues::Flat(Value::Short(ValueFormat::Spectrum(shorts)))) = &values {
            let enums = shorts.clone().cast::<DevEnum, 1>(shorts.dim());
            values = Some(AttrValues::Flat(Value::from(enums.expect("a short is a valid enumeration"))));
        }

    }

    let time = buf.read::<SystemTime>()?;

    // Shared out of the reply rather than borrowed from it, so that it outlives the reader.
    let name = buf.read::<Scalar<str>>()?;
    let r_x = buf.read_sequence_len()?;
    let r_y = buf.read_sequence_len()?;

    let mut w_x = 0;
    let mut w_y = 0;
    let mut err = None;
    if version >= DevVersion::V3 {
        w_x = buf.read_sequence_len()?;
        w_y = buf.read_sequence_len()?;
        err = buf.read::<Option<Error>>()?;
    }

    let read = if let Some(err) = err {
        Err(err)
    } else {
        let (value, w_value) = match values {
            Some(AttrValues::Flat(values)) => split_attr_values(Some(values), format, (r_x, r_y), (w_x, w_y)),
            Some(AttrValues::Encoded(parts)) => split_attr_encoded(parts, format, (r_x, r_y), (w_x, w_y)),
            None => (None, None),
        };
        Ok(AttrRead {
            value,
            w_value,
            quality,
            time,
        })
    };

    Ok((name, time, read))

}

/// Read an `AttDataReady`, the announcement of a data ready event, which has a single
/// version: the name of the attribute, its data type and the counter the device gave.
pub fn read_attr_ready(buf: &mut CdrReader<'_>) -> Result<Named<AttrReady>> {
    let name = buf.read::<Scalar<str>>()?;
    let data_kind = attr_data_kind(buf.read_long()?)?;
    let counter = buf.read_long()?;
    Ok(Named::new(name, AttrReady { data_kind, counter }))
}

/// Write an `AttDataReady`, the mirror of [`read_attr_ready`].
pub fn write_attr_ready(buf: &mut CdrWriter, name: &str, ready: &AttrReady) -> Result<()> {
    buf.write_string(name)?;
    buf.write_long(attr_data_type(&ready.data_kind))?;
    buf.write_long(ready.counter)
}

/// Common function for all versions of attribute writing, the exact mirror of
/// [`read_attr`]: every field of the structure goes out, and not only the ones a client
/// write is read back for, because this is also what a server answers a reading with.
///
/// Note that a client only ever sends two of those four structures, `write_attributes_3`
/// taking the v1 one instead of the v3 one and no v5 variant of the operation having ever
/// been added, but a server has to answer a reading with the one its version declares.
pub fn write_attr(buf: &mut CdrWriter, version: DevVersion, name: &str, read: &Result<AttrRead<Value>>) -> Result<()> {
    let time = read.as_ref().map_or(SystemTime::UNIX_EPOCH, |read| read.time);
    write_attr_at(buf, version, name, read.as_ref(), time)
}

/// Write an attribute value structure, see [`write_attr`], going out with that time
/// whatever the time of the reading, which is how a record of a history goes out with the
/// time it was recorded at.
pub(super) fn write_attr_at(buf: &mut CdrWriter, version: DevVersion, name: &str, read: std::result::Result<&AttrRead<Value>, &Error>, time: SystemTime) -> Result<()> {

    // A failed reading has no value and no quality to send, so it goes out the way a
    // server sends one: nothing in the value, an invalid quality, zeroed dimensions and
    // the failure itself in the error list.
    let (value, w_value, quality) = match read {
        Ok(read) => (read.value.as_ref(), read.w_value.as_ref(), read.quality),
        Err(_) => (None, None, AttrQuality::Invalid),
    };

    let (
        format, 
        data_type, 
        r_dim, 
        w_dim,
    ) = write_attr_data(buf, version <= DevVersion::V3, value, w_value)?;

    buf.write(&quality)?;

    if version >= DevVersion::V4 {
        write_attr_data_format(buf, format)?;
    }

    if version >= DevVersion::V5 {
        buf.write_long(data_type)?;
    }

    buf.write(&time)?;
    buf.write_string(name)?;

    // v1 has a single pair of dimensions, having neither a written one nor a place to
    // report a failure of its own, and that pair describes whichever value the structure
    // carries: a client write carries only the written one, so those are its dimensions
    // that go out, and a server answering a reading carries the read one.
    if version >= DevVersion::V3 {
        // A structure that only carries a written value still announces the dimensions of
        // what it carries on both sides, which is what cppTango sends and therefore what
        // the reading side of this implementation expects, see [`read_attr`]. The written
        // dimensions are the ones that mean anything here, the read ones only saying that
        // the value is there.
        let r_dim = if value.is_none() && w_value.is_some() { w_dim } else { r_dim };
        write_attr_dim(buf, r_dim.0, r_dim.1)?;
        write_attr_dim(buf, w_dim.0, w_dim.1)?;
        buf.write(&read.err())?;
    } else {
        let (dim_x, dim_y) = if value.is_some() { r_dim } else { w_dim };
        write_attr_dim(buf, dim_x, dim_y)?;
    }

    Ok(())

}

pub(super) fn read_attr_values_any(buf: &mut CdrReader<'_>) -> Result<Option<Value>> {

    let tk = buf.read_ulong()?;
    if tk == TK_NULL || tk == TK_VOID {
        return Ok(None);
    } else if tk == TK_ENUM {
        let repo_id = buf.read_encapsulation()?.read_string()?;
        if repo_id != tango_repo_id!("DevState") {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("Enum repository ID {repo_id:?} is not accepted in Tango attribute data"))
                .with_append_description("Expected: DevState"))
        }
        return DevState::read_values(buf, 1).map(|state| Some(Value::from(state)));
    } else if tk != TK_ALIAS {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_description(format!("Typecode kind {tk} is not accepted in Tango attribute data")));
    }

    let repo_id = buf.read_encapsulation()?.read_string()?;

    let kind = match &*repo_id {
        tango_repo_id!("DevVarBooleanArray") => AttrDataKind::Bool,
        tango_repo_id!("DevVarCharArray") => AttrDataKind::Octet,
        tango_repo_id!("DevVarUShortArray") => AttrDataKind::UShort,
        tango_repo_id!("DevVarShortArray") => AttrDataKind::Short,
        tango_repo_id!("DevVarULongArray") => AttrDataKind::ULong,
        tango_repo_id!("DevVarLongArray") => AttrDataKind::Long,
        tango_repo_id!("DevVarULong64Array") => AttrDataKind::ULong64,
        tango_repo_id!("DevVarLong64Array") => AttrDataKind::Long64,
        tango_repo_id!("DevVarFloatArray") => AttrDataKind::Float,
        tango_repo_id!("DevVarDoubleArray") => AttrDataKind::Double,
        tango_repo_id!("DevVarStateArray") => AttrDataKind::State,
        tango_repo_id!("DevVarStringArray") => AttrDataKind::String,
        repo_id => return Err(Error::new(ErrorKind::InvalidData)
            .with_description(format!("Alias repository ID {repo_id:?} is not accepted in Tango attribute data"))
            .with_append_description("Expected: DevVar*Array")),
    };

    read_attr_values(buf, kind).map(Some)

}

/// The values of both directions of an attribute, as the single sequence they travel in.
enum AttrValues {
    /// A sequence of elements, which the dimensions tell how to split, see
    /// [`split_attr_values`].
    Flat(Value),
    /// A sequence of encoded values, one per direction, see [`split_attr_encoded`].
    Encoded(Vec<DevEncoded>),
}

fn read_attr_values_union(buf: &mut CdrReader<'_>) -> Result<Option<AttrValues>> {

    let discriminant = buf.read_ulong()?;
    let kind = match discriminant {
        ATT_BOOL => AttrDataKind::Bool,
        ATT_SHORT => AttrDataKind::Short,
        ATT_LONG => AttrDataKind::Long,
        ATT_LONG64 => AttrDataKind::Long64,
        ATT_FLOAT => AttrDataKind::Float,
        ATT_DOUBLE => AttrDataKind::Double,
        ATT_UCHAR => AttrDataKind::Octet,
        ATT_USHORT => AttrDataKind::UShort,
        ATT_ULONG => AttrDataKind::ULong,
        ATT_ULONG64 => AttrDataKind::ULong64,
        ATT_STRING => AttrDataKind::String,
        ATT_STATE => AttrDataKind::State,
        // A single state, and not a sequence of them.
        ATT_DEVICE_STATE => return DevState::read_values(buf, 1).map(|state| Some(AttrValues::Flat(Value::from(state)))),
        ATT_ENCODED => {
            // Not reserved upfront, the length being only what the message claims.
            let len = buf.read_sequence_len()?;
            let mut parts = Vec::new();
            for _ in 0..len {
                parts.push(read_encoded(buf)?);
            }
            return Ok(Some(AttrValues::Encoded(parts)));
        }
        ATT_NO_DATA => {
            let _no_data = buf.read_bool()?;
            return Ok(None);
        }
        _ => return Err(Error::not_implemented()
            .with_description(format!("Attribute value union discriminant {discriminant} is not implemented"))),
    };

    read_attr_values(buf, kind).map(|values| Some(AttrValues::Flat(values)))

}

/// Read the sequence the values of an attribute travel in, which is the same whether the
/// `any` of v1 or the union of v4 announced it.
fn read_attr_values(buf: &mut CdrReader<'_>, kind: AttrDataKind) -> Result<Value> {
    match kind {
        AttrDataKind::Bool => read_spectrum::<bool>(buf),
        AttrDataKind::Octet => read_spectrum::<u8>(buf),
        AttrDataKind::UShort => read_spectrum::<u16>(buf),
        AttrDataKind::Short => read_spectrum::<i16>(buf),
        AttrDataKind::ULong => read_spectrum::<u32>(buf),
        AttrDataKind::Long => read_spectrum::<i32>(buf),
        AttrDataKind::ULong64 => read_spectrum::<u64>(buf),
        AttrDataKind::Long64 => read_spectrum::<i64>(buf),
        AttrDataKind::Float => read_spectrum::<f32>(buf),
        AttrDataKind::Double => read_spectrum::<f64>(buf),
        AttrDataKind::Enum => read_spectrum::<DevEnum>(buf),
        AttrDataKind::State => read_spectrum::<DevState>(buf),
        AttrDataKind::String => read_spectrum::<str>(buf),
        AttrDataKind::Encoded => Err(Error::new(ErrorKind::InvalidData)
            .with_description("Encoded attribute values are not a sequence of elements")),
    }
}

/// Write the values of one direction of an attribute, without the length that precedes
/// the sequence both of them share, and give back what it announces.
fn write_attr_values(buf: &mut CdrWriter, value: Option<&Value>) -> Result<(Option<AttrDataFormat>, (usize, usize), usize)> {

    fn part<T: CdrTangoElement + ?Sized>(buf: &mut CdrWriter, format: &ValueFormat<T>) -> Result<(Option<AttrDataFormat>, (usize, usize), usize)> {
        let (format, dim, len) = match format {
            ValueFormat::Scalar(value) => {
                T::write_values(buf, value)?;
                (AttrDataFormat::Scalar, (1, 0), 1)
            }
            ValueFormat::Spectrum(values) => {
                T::write_values(buf, values)?;
                (AttrDataFormat::Spectrum(0), (values.len(), 0), values.len())
            }
            ValueFormat::Image(values) => {
                T::write_values(buf, values)?;
                let [x, y] = values.dim();
                (AttrDataFormat::Image(0, 0), (x, y), values.len())
            }
        };
        Ok((Some(format), dim, len))
    }

    let Some(value) = value else {
        return Ok((None, (0, 0), 0));
    };

    match value {
        Value::Bool(format) => part(buf, format),
        Value::Octet(format) => part(buf, format),
        Value::UShort(format) => part(buf, format),
        Value::Short(format) => part(buf, format),
        Value::ULong(format) => part(buf, format),
        Value::Long(format) => part(buf, format),
        Value::ULong64(format) => part(buf, format),
        Value::Long64(format) => part(buf, format),
        Value::Float(format) => part(buf, format),
        Value::Double(format) => part(buf, format),
        Value::Enum(format) => part(buf, format),
        Value::State(format) => part(buf, format),
        Value::String(format) => part(buf, format),
        value => Err(Error::new(ErrorKind::InvalidData)
            .with_description("Failed to encode data as a Tango attribute value")
            .with_append_description(format_args!("Got: {value:?}"))),
    }

}

/// Write the value of an attribute, which is a single sequence holding both directions:
/// the read part comes first and the write part right after it, only the two pairs of
/// dimensions telling them apart. Returns what the enclosing structure has to announce.
fn write_attr_data(
    buf: &mut CdrWriter,
    any: bool,
    value: Option<&Value>,
    w_value: Option<&Value>,
) -> Result<(Option<AttrDataFormat>, i32, (usize, usize), (usize, usize))> {

    /// How the element type of that value is announced, see [`CdrTangoElement`].
    fn codes<T: CdrTangoElement + ?Sized>(_: &ValueFormat<T>) -> (u32, &'static str, u32, i32) {
        (T::TK, T::ARRAY_REPO_ID, T::ATT, T::DEV_TYPE)
    }

    // Both directions share the type of the attribute, so whichever one is there tells
    // what the value is made of, and neither of them is a value the device has nothing to
    // put in: the Any of v1 then stays empty and the union of v4 picks its no-data arm.
    let Some(first) = value.or(w_value) else {
        if any {
            buf.write_ulong(TK_NULL)?;
        } else {
            buf.write_ulong(ATT_NO_DATA)?;
            buf.write_bool(false)?;
        }
        return Ok((None, DATA_TYPE_UNKNOWN, (0, 0), (0, 0)));
    };

    // Sharing the type also means that a read-write attribute cannot have one of each,
    // which would otherwise go out announced as the type of its read value only.
    if let (Some(value), Some(w_value)) = (value, w_value)
    && mem::discriminant(value) != mem::discriminant(w_value) {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_description("The two directions of an attribute must share their type")
            .with_append_description(format_args!("Got: {value:?} and {w_value:?}")));
    }

    if let Value::Encoded(_) = first {
        return write_attr_encoded(buf, any, value, w_value);
    }

    let (tk, array_repo_id, discriminant, data_type) = match first {
        Value::Bool(format) => codes(format),
        Value::Octet(format) => codes(format),
        Value::UShort(format) => codes(format),
        Value::Short(format) => codes(format),
        Value::ULong(format) => codes(format),
        Value::Long(format) => codes(format),
        Value::ULong64(format) => codes(format),
        Value::Long64(format) => codes(format),
        Value::Float(format) => codes(format),
        Value::Double(format) => codes(format),
        Value::Enum(format) => codes(format),
        Value::State(format) => codes(format),
        Value::String(format) => codes(format),
        value => return Err(Error::new(ErrorKind::InvalidData)
            .with_description("Failed to encode data as a Tango attribute value")
            .with_append_description(format_args!("Got: {value:?}"))),
    };

    if any {
        // The Any of v1 always aliases a DevVar*Array, even for a scalar.
        write_array_tc(buf, tk, array_repo_id)?;
    } else {
        buf.write_ulong(discriminant)?;
    }

    // The length of the sequence is only known once both parts are out, so it is left
    // behind and patched afterward, the way a message length is, see `giop::MessageLen`.
    let len_pos = buf.position();
    buf.write_sequence_len(0)?;

    let (r_format, r_dim, r_len) = write_attr_values(buf, value)?;
    let (w_format, w_dim, w_len) = write_attr_values(buf, w_value)?;

    let end = buf.position();
    buf.set_position(len_pos);
    buf.write_sequence_len(r_len + w_len)?;
    buf.set_position(end);

    // Both directions of an attribute always share its format too, so either one answers
    // for the pair and only their absence leaves it unknown.
    Ok((r_format.or(w_format), data_type, r_dim, w_dim))

}

/// Write the values of an encoded attribute, the sequence of one value per direction a
/// `DevVarEncodedArray` is, which only the union of v4 onward has an arm for: cppTango
/// refuses such an attribute to the clients of an earlier version, and so does this.
fn write_attr_encoded(
    buf: &mut CdrWriter,
    any: bool,
    value: Option<&Value>,
    w_value: Option<&Value>,
) -> Result<(Option<AttrDataFormat>, i32, (usize, usize), (usize, usize))> {

    if any {
        return Err(Error::new(ErrorKind::Reason(reason::API_NotSupportedFeature))
            .with_description("The DevEncoded data type is only available to devices of IDL 4 and above"));
    }

    // Both are encoded values or absent, the caller having checked that they share their type.
    fn encoded(value: Option<&Value>) -> Option<&DevEncoded> {
        match value {
            Some(Value::Encoded(encoded)) => Some(encoded),
            _ => None,
        }
    }

    let (value, w_value) = (encoded(value), encoded(w_value));

    buf.write_ulong(ATT_ENCODED)?;
    buf.write_sequence_len(value.is_some() as usize + w_value.is_some() as usize)?;

    let mut dim = |part: Option<&DevEncoded>| -> Result<(usize, usize)> {
        match part {
            Some(part) => write_encoded(buf, part).map(|()| (1, 0)),
            None => Ok((0, 0)),
        }
    };
    let r_dim = dim(value)?;
    let w_dim = dim(w_value)?;

    Ok((Some(AttrDataFormat::Scalar), DEV_ENCODED, r_dim, w_dim))

}

/// Cut the read and the written values of an attribute out of the single spectrum both
/// travel in, in that order, their formats being told by [`attr_formats`].
pub(super) fn split_attr_values(
    values: Option<Value>,
    format: Option<AttrDataFormat>,
    r_dim: (usize, usize),
    w_dim: (usize, usize),
) -> (Option<Value>, Option<Value>) {

    fn split<T: TangoElement + ?Sized>(
        values: ValueFormat<T>,
        format: Option<AttrDataFormat>,
        r_dim: (usize, usize),
        w_dim: (usize, usize),
    ) -> (Option<Value>, Option<Value>) {

        let Ok(values) = values.into_spectrum() else {
            return (None, None);
        };

        let (r_format, w_format) = attr_formats(format, r_dim, w_dim, values.len());

        // Every part shares the octets of the whole, and the formats account for exactly
        // as many values as there are, see [`attr_formats`].
        let mut at = 0;
        let mut take = |format: AttrDataFormat| {
            let len = match format {
                AttrDataFormat::Scalar => 1,
                AttrDataFormat::Spectrum(x) => x,
                AttrDataFormat::Image(x, y) => x * y,
            };
            let part = values.slice(at..at + len);
            at += len;
            T::wrap(match format {
                AttrDataFormat::Scalar => ValueFormat::Scalar(part.into_scalar().unwrap()),
                AttrDataFormat::Spectrum(_) => ValueFormat::Spectrum(part),
                AttrDataFormat::Image(x, y) => ValueFormat::Image(part.reshape([x, y]).unwrap()),
            })
        };

        (r_format.map(&mut take), w_format.map(take))

    }

    match values {
        Some(Value::Bool(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Octet(values)) => split(values, format, r_dim, w_dim),
        Some(Value::UShort(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Short(values)) => split(values, format, r_dim, w_dim),
        Some(Value::ULong(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Long(values)) => split(values, format, r_dim, w_dim),
        Some(Value::ULong64(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Long64(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Float(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Double(values)) => split(values, format, r_dim, w_dim),
        Some(Value::Enum(values)) => split(values, format, r_dim, w_dim),
        Some(Value::State(values)) => split(values, format, r_dim, w_dim),
        Some(Value::String(values)) => split(values, format, r_dim, w_dim),
        Some(_) | None => (None, None),
    }

}

/// Take the read and the written values of an encoded attribute out of the sequence both
/// travel in, one value each and in that order, their formats being told by
/// [`attr_formats`] like those of any other attribute.
fn split_attr_encoded(
    parts: Vec<DevEncoded>,
    format: Option<AttrDataFormat>,
    r_dim: (usize, usize),
    w_dim: (usize, usize),
) -> (Option<Value>, Option<Value>) {

    let (r_format, w_format) = attr_formats(format, r_dim, w_dim, parts.len());

    // An encoded value is only ever a scalar, so any other format leaves the part out.
    let mut parts = parts.into_iter();
    let mut take = |format: AttrDataFormat| match format {
        AttrDataFormat::Scalar => parts.next().map(Value::Encoded),
        _ => None,
    };

    (r_format.and_then(&mut take), w_format.and_then(take))

}

/// The formats, with their dimensions, of the read and the written values of an attribute
/// whose single spectrum holds `len` values, out of the format and the two pairs of
/// dimensions that were announced along it, both of them being absent when those make no
/// sense of that many values.
fn attr_formats(
    format: Option<AttrDataFormat>,
    r_dim: (usize, usize),
    w_dim: (usize, usize),
    len: usize,
) -> (Option<AttrDataFormat>, Option<AttrDataFormat>) {

    use AttrDataFormat::{Scalar, Spectrum, Image};

    match format {
        Some(AttrDataFormat::Scalar) => {
            match (r_dim, w_dim, len) {
                ((1, 0), (0, 0), 1) => ( // Read-only
                    Some(Scalar),
                    None
                ),
                // The read dimensions are not looked at, the way they are not in the two
                // branches below: a structure carrying only a written value is described
                // by the written ones, and the read ones are whatever the sender left
                // there — see the note on the guessing branch.
                ((_, _), (1, 0), 1) => ( // Write-only
                    None,
                    Some(Scalar),
                ),
                ((1, 0), (1, 0), 2) => ( // Read-write
                    Some(Scalar),
                    Some(Scalar),
                ),
                _ => (None, None)
            }
        }
        // A known format is only ever announced along a reply, whose read dimensions are
        // set, so the read-write arms come before the write-only ones: an empty read value
        // along a written one is still a read value, and not dropped.
        Some(AttrDataFormat::Spectrum(_)) => {
            match (r_dim, w_dim) {
                ((r_x, 0), (0, 0)) if r_x == len => ( // Read-only
                    Some(Spectrum(r_x)),
                    None,
                ),
                ((r_x, 0), (w_x, 0)) if r_x + w_x == len => ( // Read-write
                    Some(Spectrum(r_x)),
                    Some(Spectrum(w_x)),
                ),
                ((_, 0), (w_x, 0)) if w_x == len => ( // Write-only
                    None,
                    Some(Spectrum(w_x)),
                ),
                _ => (None, None)
            }
        }
        Some(AttrDataFormat::Image(_, _)) => {
            match (r_dim, w_dim) {
                ((r_x, r_y), (0, 0)) if r_x * r_y == len => ( // Read-only
                    Some(Image(r_x, r_y)),
                    None,
                ),
                ((r_x, r_y), (w_x, w_y)) if r_x * r_y + w_x * w_y == len => ( // Read-write
                    Some(Image(r_x, r_y)),
                    Some(Image(w_x, w_y)),
                ),
                ((_, _), (w_x, w_y)) if w_x * w_y == len => ( // Write-only
                    None,
                    Some(Image(w_x, w_y)),
                ),
                _ => (None, None)
            }
        }
        None => {

            // We must guess the data format from the dimensions, note that this is used
            // for read_attributes v1 and v3, note that v1 don't even have different 
            // dimensions for read and write.
            //
            // What's known in v1:
            // - Not possible to distinguish read-only from write-only for scalars;
            // - Can only read read-only spectrums or images;
            //
            // It is also the branch a write request from cppTango lands in, which is what
            // the three write-only arms come first for. Such a request announces
            // `FMT_UNKNOWN` and fills in the written dimensions, and leaves the read ones
            // as it found them: `AttributeDim` is generated without a constructor and a
            // client never sets that pair, so they are the halves of a pointer as often as
            // not. They are therefore not looked at where the written ones already account
            // for the whole sequence — reading them cost a scalar its shape, a garbage
            // `r_dim` with a zero `dim_y` matching the spectrum arm and making a spectrum
            // of one out of it, and cost it its value outright otherwise.

            match (r_dim, w_dim) {
                ((_, _), (1, 0)) if len == 1 => ( // Write-only, and only that
                    None,
                    Some(Scalar),
                ),
                ((_, _), (w_x, 0)) if w_x == len => ( // Write-only spectrum
                    None,
                    Some(Spectrum(w_x)),
                ),
                ((_, _), (w_x, w_y)) if w_x * w_y == len => ( // Write-only image
                    None,
                    Some(Image(w_x, w_y)),
                ),
                ((1, 0), (0, 0)) if len == 1 => ( // Read-only
                    Some(Scalar),
                    None,
                ),
                ((1, 0), (0, 0) | (1, 0)) if len == 2 => ( // Read-write
                    Some(Scalar),
                    Some(Scalar),
                ),
                ((r_x, 0), (0, 0)) if r_x == len => ( // Read-only
                    Some(Spectrum(r_x)),
                    None,
                ),
                ((r_x, 0), (w_x, 0)) if r_x + w_x == len => ( // Read-write (v3 only)
                    Some(Spectrum(r_x)),
                    Some(Spectrum(w_x)),
                ),
                ((r_x, r_y), (0, 0)) if r_x * r_y == len => ( // Read-only
                    Some(Image(r_x, r_y)),
                    None,
                ),
                ((r_x, r_y), (w_x, w_y)) if r_x * r_y + w_x * w_y == len => ( // Read-write (v3 only)
                    Some(Image(r_x, r_y)),
                    Some(Image(w_x, w_y)),
                ),
                _ => (None, None)
            }

        }
    }

}


#[cfg(test)]
mod tests {

    use super::*;

    use crate::value::{Image, Spectrum};

    /// An encoded reading goes out as the sequence of one value per direction it travels
    /// in, and comes back as it was, whichever directions it has.
    #[test]
    fn encoded_attr() {

        let read = Value::from(DevEncoded::new("JPEG_RGB", vec![1u8, 2, 3]));
        let written = Value::from(DevEncoded::new("RAW", vec![4u8]));

        for version in [DevVersion::V4, DevVersion::V5] {
            for (value, w_value) in [
                (Some(read.clone()), Some(written.clone())),
                (Some(read.clone()), None),
                (None, Some(written.clone())),
            ] {

                let read = AttrRead { value, w_value, quality: AttrQuality::Valid, time: SystemTime::UNIX_EPOCH };

                let mut writer = CdrWriter::new();
                write_attr(&mut writer, version, "attr", &Ok(read.clone())).unwrap();
                let buf = writer.into_buf().freeze();

                let back = read_attr(&mut CdrReader::new(&buf), version).unwrap().value;
                assert!(!buf.is_unique(), "the values were copied out of the reply");
                assert_eq!(back.unwrap(), read, "{version:?}");

            }
        }

    }

    /// An encoded reading cannot go out to a client of IDL 3 or earlier, which has no
    /// place for it, the way cppTango refuses it to them.
    #[test]
    fn encoded_attr_any() {
        let read = AttrRead::new(Value::from(DevEncoded::new("RAW", vec![1u8])));
        let mut writer = CdrWriter::new();
        assert!(write_attr(&mut writer, DevVersion::V3, "attr", &Ok(read)).is_err());
    }

    /// A write request from cppTango leaves its read dimensions uninitialized, so the
    /// written ones are what the value has to be shaped by: a scalar stays a scalar
    /// whatever is in the pair nobody set.
    ///
    /// It neither becomes a spectrum of one when the garbage happens to have a zero `dim_y`,
    /// nor is dropped altogether otherwise.
    #[test]
    fn written_scalar() {

        for r_dim in [(671181954, 32662), (3758189698, 0), (0, 0), (1, 0)] {

            let (value, w_value) = split_attr_values(Some(Value::from(vec![1e300])), None, r_dim, (1, 0));

            assert!(value.is_none(), "a write request carries no read value, {r_dim:?}");
            assert_eq!(w_value, Some(Value::from(1e300)), "with a read dim of {r_dim:?}");

        }

    }

    /// The same for a spectrum, whose length is the written dimension and not the pair
    /// that was never set.
    #[test]
    fn written_spectrum() {

        for r_dim in [(671181954, 32662), (3758189698, 0), (0, 0), (3, 0)] {

            let written = Value::from(vec![1.0, -2.0, 3.5]);
            let (value, w_value) = split_attr_values(Some(written.clone()), None, r_dim, (3, 0));

            assert!(value.is_none(), "a write request carries no read value, {r_dim:?}");
            assert_eq!(w_value, Some(written), "with a read dim of {r_dim:?}");

        }

    }

    /// What a read reply carries is still read by its own dimensions, both values of a
    /// read-write attribute included, which is what the arms above the write-only ones
    /// have to keep doing.
    #[test]
    fn read_values() {

        let (value, w_value) = split_attr_values(Some(Value::from(vec![1.5, 2.5])), None, (1, 0), (1, 0));
        assert_eq!(value, Some(Value::from(1.5)));
        assert_eq!(w_value, Some(Value::from(2.5)));

        let (value, w_value) = split_attr_values(Some(Value::from(vec![1.5])), None, (1, 0), (0, 0));
        assert_eq!(value, Some(Value::from(1.5)));
        assert!(w_value.is_none(), "a read-only attribute has no written value");

        let read = Value::from(vec![1.0, 2.0, 3.0]);
        let (value, w_value) = split_attr_values(Some(read.clone()), None, (3, 0), (0, 0));
        assert_eq!(value, Some(read));
        assert!(w_value.is_none(), "a read-only attribute has no written value");

    }

    /// An empty read value along a written one, which is what cppTango answers for a
    /// read-write spectrum or image read empty after a write: the read value is kept.
    #[test]
    fn empty_read_values() {

        let written = Value::from(vec![-1.5, 0.0, 2.25, 1e-300]);
        let (value, w_value) = split_attr_values(Some(written.clone()), Some(AttrDataFormat::Spectrum(0)), (0, 0), (4, 0));
        assert_eq!(value, Some(Value::from(Vec::<f64>::new())));
        assert_eq!(w_value, Some(written));

        let (value, w_value) = split_attr_values(Some(Value::from(vec![1.0, 2.0])), Some(AttrDataFormat::Image(0, 0)), (0, 0), (2, 1));
        assert!(value.is_some(), "an empty read image is still a read value");
        assert!(w_value.is_some(), "the written image is kept");

    }

    /// A reading goes out and comes back as it was, its values sharing the octets of the
    /// reply: an image of both directions, and an enumeration, which only v5 tells from the
    /// short it travels as.
    #[test]
    fn attr_round_trip() {

        let image = Value::from(Image::<f64>::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [3, 2]).unwrap());
        let enums = Value::from(Spectrum::<DevEnum>::from(vec![DevEnum(1), DevEnum(2)]));

        for (version, value) in [(DevVersion::V4, image.clone()), (DevVersion::V5, image), (DevVersion::V5, enums)] {

            let read = AttrRead {
                value: Some(value.clone()),
                w_value: Some(value),
                quality: AttrQuality::Valid,
                time: SystemTime::UNIX_EPOCH,
            };

            let mut writer = CdrWriter::new();
            write_attr(&mut writer, version, "attr", &Ok(read.clone())).unwrap();
            let buf = writer.into_buf().freeze();

            let Named { name, value: back } = read_attr(&mut CdrReader::new(&buf), version).unwrap();
            let back = back.unwrap();
            assert_eq!(name.value(), "attr");
            assert!(!buf.is_unique(), "the values were copied out of the reply");
            assert_eq!(back, read, "{version:?}");

        }


    }

}