Skip to main content

zerodds_idl_cpp/
qos.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Block-G: QoS-Policy + Type-Traits (Spec idl4-cpp-1.0 §7.5).
4//!
5//! Emittiert die 22 OMG-DDS-1.4-QoS-Policies als C++17-Strukturen in den
6//! Namespace `dds::core::policy`. Jede Policy bekommt:
7//! - Default-Konstruktor + Special-Members,
8//! - Reference-Pattern Getter (mutable + const),
9//! - Setter-Paar (`const T&` + `T&&` Move-Setter),
10//! - Equality-Operator `==`/`!=` (sauberer Vergleich, falls relevant).
11//!
12//! Spec-Tabelle: DDS 1.4 §2.2.3 (Tabelle 2.6/2.7) plus DDS-XTypes §7.6.4
13//! fuer DataRepresentation/TypeConsistencyEnforcement.
14//!
15//! Zusaetzlich werden die Type-Traits aus idl4-cpp §7.1.4 Tab.7.1
16//! (`value_type<T>`, `in_type<T>`, `out_type<T>`, `inout_type<T>`) als
17//! Templates im Namespace `dds::core` emittiert.
18
19use core::fmt::Write;
20
21use crate::error::CppGenError;
22
23/// Beschreibung eines QoS-Policy-Felds.
24#[derive(Debug, Clone, Copy)]
25struct QosField {
26    /// Feld-Name (snake_case).
27    name: &'static str,
28    /// C++-Typ-Ausdruck.
29    cpp_ty: &'static str,
30    /// Default-Wert (C++-Initialisierungsausdruck).
31    default_init: &'static str,
32}
33
34/// Beschreibung einer QoS-Policy-Klasse.
35#[derive(Debug, Clone, Copy)]
36struct QosSpec {
37    /// Klassen-Name (PascalCase).
38    name: &'static str,
39    /// Felder der Policy.
40    fields: &'static [QosField],
41    /// Spec-Referenz (DDS 1.4 §2.2.3 + Tabellen-Zeile).
42    spec_ref: &'static str,
43}
44
45/// 22 QoS-Policies aus OMG DDS 1.4. Reihenfolge folgt Tabelle 2.6 +
46/// Erweiterungen aus DDS-Security 1.1 / DDS-XTypes 1.3.
47#[allow(clippy::too_many_lines)]
48const POLICIES: &[QosSpec] = &[
49    QosSpec {
50        name: "UserDataQosPolicy",
51        spec_ref: "§2.2.3.1",
52        fields: &[QosField {
53            name: "value",
54            cpp_ty: "std::vector<uint8_t>",
55            default_init: "{}",
56        }],
57    },
58    QosSpec {
59        name: "TopicDataQosPolicy",
60        spec_ref: "§2.2.3.2",
61        fields: &[QosField {
62            name: "value",
63            cpp_ty: "std::vector<uint8_t>",
64            default_init: "{}",
65        }],
66    },
67    QosSpec {
68        name: "GroupDataQosPolicy",
69        spec_ref: "§2.2.3.3",
70        fields: &[QosField {
71            name: "value",
72            cpp_ty: "std::vector<uint8_t>",
73            default_init: "{}",
74        }],
75    },
76    QosSpec {
77        name: "DurabilityQosPolicy",
78        spec_ref: "§2.2.3.4",
79        fields: &[QosField {
80            name: "kind",
81            cpp_ty: "::dds::core::policy::DurabilityKind",
82            default_init: "::dds::core::policy::DurabilityKind::Volatile",
83        }],
84    },
85    QosSpec {
86        name: "DurabilityServiceQosPolicy",
87        spec_ref: "§2.2.3.5",
88        fields: &[
89            QosField {
90                name: "service_cleanup_delay",
91                cpp_ty: "::dds::core::Duration",
92                default_init: "::dds::core::Duration::zero()",
93            },
94            QosField {
95                name: "history_kind",
96                cpp_ty: "::dds::core::policy::HistoryKind",
97                default_init: "::dds::core::policy::HistoryKind::KeepLast",
98            },
99            QosField {
100                name: "history_depth",
101                cpp_ty: "int32_t",
102                default_init: "1",
103            },
104            QosField {
105                name: "max_samples",
106                cpp_ty: "int32_t",
107                default_init: "-1",
108            },
109            QosField {
110                name: "max_instances",
111                cpp_ty: "int32_t",
112                default_init: "-1",
113            },
114            QosField {
115                name: "max_samples_per_instance",
116                cpp_ty: "int32_t",
117                default_init: "-1",
118            },
119        ],
120    },
121    QosSpec {
122        name: "PresentationQosPolicy",
123        spec_ref: "§2.2.3.6",
124        fields: &[
125            QosField {
126                name: "access_scope",
127                cpp_ty: "::dds::core::policy::PresentationAccessScopeKind",
128                default_init: "::dds::core::policy::PresentationAccessScopeKind::Instance",
129            },
130            QosField {
131                name: "coherent_access",
132                cpp_ty: "bool",
133                default_init: "false",
134            },
135            QosField {
136                name: "ordered_access",
137                cpp_ty: "bool",
138                default_init: "false",
139            },
140        ],
141    },
142    QosSpec {
143        name: "DeadlineQosPolicy",
144        spec_ref: "§2.2.3.7",
145        fields: &[QosField {
146            name: "period",
147            cpp_ty: "::dds::core::Duration",
148            default_init: "::dds::core::Duration::infinite()",
149        }],
150    },
151    QosSpec {
152        name: "LatencyBudgetQosPolicy",
153        spec_ref: "§2.2.3.8",
154        fields: &[QosField {
155            name: "duration",
156            cpp_ty: "::dds::core::Duration",
157            default_init: "::dds::core::Duration::zero()",
158        }],
159    },
160    QosSpec {
161        name: "OwnershipQosPolicy",
162        spec_ref: "§2.2.3.9",
163        fields: &[QosField {
164            name: "kind",
165            cpp_ty: "::dds::core::policy::OwnershipKind",
166            default_init: "::dds::core::policy::OwnershipKind::Shared",
167        }],
168    },
169    QosSpec {
170        name: "OwnershipStrengthQosPolicy",
171        spec_ref: "§2.2.3.10",
172        fields: &[QosField {
173            name: "value",
174            cpp_ty: "int32_t",
175            default_init: "0",
176        }],
177    },
178    QosSpec {
179        name: "LivelinessQosPolicy",
180        spec_ref: "§2.2.3.11",
181        fields: &[
182            QosField {
183                name: "kind",
184                cpp_ty: "::dds::core::policy::LivelinessKind",
185                default_init: "::dds::core::policy::LivelinessKind::Automatic",
186            },
187            QosField {
188                name: "lease_duration",
189                cpp_ty: "::dds::core::Duration",
190                default_init: "::dds::core::Duration::infinite()",
191            },
192        ],
193    },
194    QosSpec {
195        name: "TimeBasedFilterQosPolicy",
196        spec_ref: "§2.2.3.12",
197        fields: &[QosField {
198            name: "minimum_separation",
199            cpp_ty: "::dds::core::Duration",
200            default_init: "::dds::core::Duration::zero()",
201        }],
202    },
203    QosSpec {
204        name: "PartitionQosPolicy",
205        spec_ref: "§2.2.3.13",
206        fields: &[QosField {
207            name: "name",
208            cpp_ty: "std::vector<std::string>",
209            default_init: "{}",
210        }],
211    },
212    QosSpec {
213        name: "ReliabilityQosPolicy",
214        spec_ref: "§2.2.3.14",
215        fields: &[
216            QosField {
217                name: "kind",
218                cpp_ty: "::dds::core::policy::ReliabilityKind",
219                default_init: "::dds::core::policy::ReliabilityKind::BestEffort",
220            },
221            QosField {
222                name: "max_blocking_time",
223                cpp_ty: "::dds::core::Duration",
224                default_init: "::dds::core::Duration::from_millis(100)",
225            },
226        ],
227    },
228    QosSpec {
229        name: "TransportPriorityQosPolicy",
230        spec_ref: "§2.2.3.15",
231        fields: &[QosField {
232            name: "value",
233            cpp_ty: "int32_t",
234            default_init: "0",
235        }],
236    },
237    QosSpec {
238        name: "LifespanQosPolicy",
239        spec_ref: "§2.2.3.16",
240        fields: &[QosField {
241            name: "duration",
242            cpp_ty: "::dds::core::Duration",
243            default_init: "::dds::core::Duration::infinite()",
244        }],
245    },
246    QosSpec {
247        name: "DestinationOrderQosPolicy",
248        spec_ref: "§2.2.3.17",
249        fields: &[QosField {
250            name: "kind",
251            cpp_ty: "::dds::core::policy::DestinationOrderKind",
252            default_init: "::dds::core::policy::DestinationOrderKind::ByReceptionTimestamp",
253        }],
254    },
255    QosSpec {
256        name: "HistoryQosPolicy",
257        spec_ref: "§2.2.3.18",
258        fields: &[
259            QosField {
260                name: "kind",
261                cpp_ty: "::dds::core::policy::HistoryKind",
262                default_init: "::dds::core::policy::HistoryKind::KeepLast",
263            },
264            QosField {
265                name: "depth",
266                cpp_ty: "int32_t",
267                default_init: "1",
268            },
269        ],
270    },
271    QosSpec {
272        name: "ResourceLimitsQosPolicy",
273        spec_ref: "§2.2.3.19",
274        fields: &[
275            QosField {
276                name: "max_samples",
277                cpp_ty: "int32_t",
278                default_init: "-1",
279            },
280            QosField {
281                name: "max_instances",
282                cpp_ty: "int32_t",
283                default_init: "-1",
284            },
285            QosField {
286                name: "max_samples_per_instance",
287                cpp_ty: "int32_t",
288                default_init: "-1",
289            },
290        ],
291    },
292    QosSpec {
293        name: "EntityFactoryQosPolicy",
294        spec_ref: "§2.2.3.20",
295        fields: &[QosField {
296            name: "autoenable_created_entities",
297            cpp_ty: "bool",
298            default_init: "true",
299        }],
300    },
301    QosSpec {
302        name: "WriterDataLifecycleQosPolicy",
303        spec_ref: "§2.2.3.21",
304        fields: &[QosField {
305            name: "autodispose_unregistered_instances",
306            cpp_ty: "bool",
307            default_init: "true",
308        }],
309    },
310    QosSpec {
311        name: "ReaderDataLifecycleQosPolicy",
312        spec_ref: "§2.2.3.22",
313        fields: &[
314            QosField {
315                name: "autopurge_nowriter_samples_delay",
316                cpp_ty: "::dds::core::Duration",
317                default_init: "::dds::core::Duration::infinite()",
318            },
319            QosField {
320                name: "autopurge_disposed_samples_delay",
321                cpp_ty: "::dds::core::Duration",
322                default_init: "::dds::core::Duration::infinite()",
323            },
324        ],
325    },
326];
327
328/// Schreibt den vollstaendigen QoS-Header.
329///
330/// # Errors
331/// Liefert [`CppGenError::Internal`], wenn das Schreiben in den
332/// `String`-Buffer scheitert.
333pub fn emit_qos_header(out: &mut String) -> Result<(), CppGenError> {
334    writeln!(out, "// Block-G: DDS-QoS-Policies (Spec dds-1.4 §2.2.3).").map_err(fmt_err)?;
335    writeln!(
336        out,
337        "namespace dds {{ namespace core {{ namespace policy {{"
338    )
339    .map_err(fmt_err)?;
340    writeln!(out).map_err(fmt_err)?;
341
342    // Kind-Enums (Forward-Decl reicht — volle Definitionen liegen in der
343    // PSM-CXX-Runtime).
344    writeln!(
345        out,
346        "// Forward-Declarations der Kind-Enums (siehe DDS 1.4 §2.2.3)."
347    )
348    .map_err(fmt_err)?;
349    for kind in [
350        "DurabilityKind",
351        "PresentationAccessScopeKind",
352        "OwnershipKind",
353        "LivelinessKind",
354        "ReliabilityKind",
355        "DestinationOrderKind",
356        "HistoryKind",
357    ] {
358        writeln!(out, "enum class {kind} : int32_t;").map_err(fmt_err)?;
359    }
360    writeln!(out, "using QosPolicyId = int32_t;").map_err(fmt_err)?;
361    writeln!(out).map_err(fmt_err)?;
362
363    for p in POLICIES {
364        emit_policy_class(out, p)?;
365    }
366
367    writeln!(out, "}} }} }} // namespace dds::core::policy").map_err(fmt_err)?;
368    writeln!(out).map_err(fmt_err)?;
369
370    emit_type_traits(out)?;
371
372    Ok(())
373}
374
375/// Liefert die Liste aller emittierten Policy-Klassen-Namen.
376#[must_use]
377pub fn policy_class_names() -> Vec<&'static str> {
378    POLICIES.iter().map(|p| p.name).collect()
379}
380
381fn emit_policy_class(out: &mut String, p: &QosSpec) -> Result<(), CppGenError> {
382    writeln!(out, "/// {} ({})", p.name, p.spec_ref).map_err(fmt_err)?;
383    writeln!(out, "class {} {{", p.name).map_err(fmt_err)?;
384    writeln!(out, "public:").map_err(fmt_err)?;
385    writeln!(out, "    {}() = default;", p.name).map_err(fmt_err)?;
386    writeln!(out, "    ~{}() = default;", p.name).map_err(fmt_err)?;
387    writeln!(out, "    {0}(const {0}&) = default;", p.name).map_err(fmt_err)?;
388    writeln!(out, "    {0}({0}&&) noexcept = default;", p.name).map_err(fmt_err)?;
389    writeln!(out, "    {0}& operator=(const {0}&) = default;", p.name).map_err(fmt_err)?;
390    writeln!(out, "    {0}& operator=({0}&&) noexcept = default;", p.name).map_err(fmt_err)?;
391    writeln!(out).map_err(fmt_err)?;
392
393    for f in p.fields {
394        // Const-Getter:
395        writeln!(
396            out,
397            "    const {ty}& {name}() const {{ return {name}_; }}",
398            ty = f.cpp_ty,
399            name = f.name
400        )
401        .map_err(fmt_err)?;
402        // Mutable-Getter:
403        writeln!(
404            out,
405            "    {ty}& {name}() {{ return {name}_; }}",
406            ty = f.cpp_ty,
407            name = f.name
408        )
409        .map_err(fmt_err)?;
410        // Setter (const-ref):
411        writeln!(
412            out,
413            "    void {name}(const {ty}& value) {{ {name}_ = value; }}",
414            ty = f.cpp_ty,
415            name = f.name
416        )
417        .map_err(fmt_err)?;
418        // Setter (Move-Variant):
419        writeln!(
420            out,
421            "    void {name}({ty}&& value) {{ {name}_ = std::move(value); }}",
422            ty = f.cpp_ty,
423            name = f.name
424        )
425        .map_err(fmt_err)?;
426    }
427
428    if !p.fields.is_empty() {
429        writeln!(out).map_err(fmt_err)?;
430        // Equality-Operator: vergleicht alle Felder.
431        write!(
432            out,
433            "    bool operator==(const {}& rhs) const {{ return ",
434            p.name
435        )
436        .map_err(fmt_err)?;
437        for (i, f) in p.fields.iter().enumerate() {
438            if i > 0 {
439                write!(out, " && ").map_err(fmt_err)?;
440            }
441            write!(out, "{name}_ == rhs.{name}_", name = f.name).map_err(fmt_err)?;
442        }
443        writeln!(out, "; }}").map_err(fmt_err)?;
444        writeln!(
445            out,
446            "    bool operator!=(const {0}& rhs) const {{ return !(*this == rhs); }}",
447            p.name
448        )
449        .map_err(fmt_err)?;
450    }
451
452    if !p.fields.is_empty() {
453        writeln!(out).map_err(fmt_err)?;
454        writeln!(out, "private:").map_err(fmt_err)?;
455        for f in p.fields {
456            writeln!(
457                out,
458                "    {ty} {name}_{{{init}}};",
459                ty = f.cpp_ty,
460                name = f.name,
461                init = f.default_init
462            )
463            .map_err(fmt_err)?;
464        }
465    }
466
467    writeln!(out, "}};").map_err(fmt_err)?;
468    writeln!(out).map_err(fmt_err)?;
469    Ok(())
470}
471
472/// Emittiert die Type-Traits aus idl4-cpp §7.1.4 Tab.7.1.
473///
474/// - `value_type<T>`: by-value Typ.
475/// - `in_type<T>`: const-ref-Argument-Typ.
476/// - `out_type<T>`: out-Parameter-Ref.
477/// - `inout_type<T>`: inout-Parameter-Ref.
478fn emit_type_traits(out: &mut String) -> Result<(), CppGenError> {
479    writeln!(out, "// idl4-cpp §7.1.4 Tab.7.1 — Argument-Type-Traits.").map_err(fmt_err)?;
480    writeln!(
481        out,
482        "namespace dds {{ namespace core {{ namespace traits {{"
483    )
484    .map_err(fmt_err)?;
485    writeln!(out).map_err(fmt_err)?;
486    writeln!(out, "template <typename T>").map_err(fmt_err)?;
487    writeln!(out, "struct value_type {{ using type = T; }};").map_err(fmt_err)?;
488    writeln!(out).map_err(fmt_err)?;
489    writeln!(out, "template <typename T>").map_err(fmt_err)?;
490    writeln!(out, "struct in_type {{ using type = const T&; }};").map_err(fmt_err)?;
491    writeln!(out).map_err(fmt_err)?;
492    writeln!(out, "template <typename T>").map_err(fmt_err)?;
493    writeln!(out, "struct out_type {{ using type = T&; }};").map_err(fmt_err)?;
494    writeln!(out).map_err(fmt_err)?;
495    writeln!(out, "template <typename T>").map_err(fmt_err)?;
496    writeln!(out, "struct inout_type {{ using type = T&; }};").map_err(fmt_err)?;
497    writeln!(out).map_err(fmt_err)?;
498    // Convenience-Aliase (`_t`-Suffix wie in std):
499    writeln!(out, "template <typename T>").map_err(fmt_err)?;
500    writeln!(out, "using value_type_t = typename value_type<T>::type;").map_err(fmt_err)?;
501    writeln!(out, "template <typename T>").map_err(fmt_err)?;
502    writeln!(out, "using in_type_t = typename in_type<T>::type;").map_err(fmt_err)?;
503    writeln!(out, "template <typename T>").map_err(fmt_err)?;
504    writeln!(out, "using out_type_t = typename out_type<T>::type;").map_err(fmt_err)?;
505    writeln!(out, "template <typename T>").map_err(fmt_err)?;
506    writeln!(out, "using inout_type_t = typename inout_type<T>::type;").map_err(fmt_err)?;
507    writeln!(out).map_err(fmt_err)?;
508    writeln!(out, "}} }} }} // namespace dds::core::traits").map_err(fmt_err)?;
509    writeln!(out).map_err(fmt_err)?;
510    Ok(())
511}
512
513fn fmt_err(_: core::fmt::Error) -> CppGenError {
514    CppGenError::Internal("string formatting failed".into())
515}
516
517#[cfg(test)]
518mod tests {
519    #![allow(clippy::expect_used, clippy::panic)]
520    use super::*;
521
522    fn render() -> String {
523        let mut s = String::new();
524        emit_qos_header(&mut s).expect("emit");
525        s
526    }
527
528    #[test]
529    fn qos_namespace_is_dds_core_policy() {
530        let s = render();
531        assert!(s.contains("namespace dds { namespace core { namespace policy {"));
532        assert!(s.contains("} } } // namespace dds::core::policy"));
533    }
534
535    #[test]
536    fn reliability_qos_policy_has_kind_and_max_blocking_time() {
537        let s = render();
538        assert!(s.contains("class ReliabilityQosPolicy {"));
539        assert!(s.contains("ReliabilityKind kind_"));
540        assert!(s.contains("::dds::core::Duration max_blocking_time_"));
541    }
542
543    #[test]
544    fn move_setter_uses_rvalue_and_std_move() {
545        let s = render();
546        assert!(s.contains("void max_blocking_time(::dds::core::Duration&& value)"));
547        assert!(s.contains("max_blocking_time_ = std::move(value);"));
548    }
549
550    #[test]
551    fn const_getter_returns_const_ref() {
552        let s = render();
553        assert!(s.contains("const ::dds::core::policy::ReliabilityKind& kind() const"));
554        assert!(s.contains("::dds::core::policy::ReliabilityKind& kind()"));
555    }
556
557    #[test]
558    fn mutable_getter_returns_mutable_ref() {
559        let s = render();
560        // The mutable getter signature without "const":
561        let needle = "::dds::core::Duration& max_blocking_time() {";
562        assert!(s.contains(needle));
563    }
564
565    #[test]
566    fn history_qos_policy_has_kind_and_depth() {
567        let s = render();
568        assert!(s.contains("class HistoryQosPolicy {"));
569        assert!(s.contains("HistoryKind kind_"));
570        assert!(s.contains("int32_t depth_{1}"));
571    }
572
573    #[test]
574    fn resource_limits_has_three_fields() {
575        let s = render();
576        assert!(s.contains("class ResourceLimitsQosPolicy {"));
577        assert!(s.contains("int32_t max_samples_{-1}"));
578        assert!(s.contains("int32_t max_instances_{-1}"));
579        assert!(s.contains("int32_t max_samples_per_instance_{-1}"));
580    }
581
582    #[test]
583    fn deadline_qos_policy_default_is_infinite() {
584        let s = render();
585        assert!(s.contains("class DeadlineQosPolicy {"));
586        assert!(s.contains("Duration period_{::dds::core::Duration::infinite()}"));
587    }
588
589    #[test]
590    fn user_data_uses_byte_vector() {
591        let s = render();
592        assert!(s.contains("class UserDataQosPolicy {"));
593        assert!(s.contains("std::vector<uint8_t> value_"));
594    }
595
596    #[test]
597    fn partition_qos_policy_uses_string_vector() {
598        let s = render();
599        assert!(s.contains("class PartitionQosPolicy {"));
600        assert!(s.contains("std::vector<std::string> name_"));
601    }
602
603    #[test]
604    fn all_22_policies_emitted() {
605        let names = policy_class_names();
606        assert_eq!(names.len(), 22);
607        let s = render();
608        for n in names {
609            assert!(s.contains(&format!("class {n} {{")), "missing: {n}");
610        }
611    }
612
613    #[test]
614    fn type_traits_namespace_emitted() {
615        let s = render();
616        assert!(s.contains("namespace dds { namespace core { namespace traits {"));
617        assert!(s.contains("struct value_type"));
618        assert!(s.contains("struct in_type"));
619        assert!(s.contains("struct out_type"));
620        assert!(s.contains("struct inout_type"));
621    }
622
623    #[test]
624    fn type_traits_value_type_is_t_by_value() {
625        let s = render();
626        assert!(s.contains("struct value_type { using type = T; };"));
627    }
628
629    #[test]
630    fn type_traits_in_type_is_const_ref() {
631        let s = render();
632        assert!(s.contains("struct in_type { using type = const T&; };"));
633    }
634
635    #[test]
636    fn type_traits_out_and_inout_are_ref() {
637        let s = render();
638        assert!(s.contains("struct out_type { using type = T&; };"));
639        assert!(s.contains("struct inout_type { using type = T&; };"));
640    }
641
642    #[test]
643    fn type_trait_aliases_with_t_suffix() {
644        let s = render();
645        assert!(s.contains("using value_type_t = typename value_type<T>::type;"));
646        assert!(s.contains("using in_type_t = typename in_type<T>::type;"));
647        assert!(s.contains("using out_type_t = typename out_type<T>::type;"));
648        assert!(s.contains("using inout_type_t = typename inout_type<T>::type;"));
649    }
650
651    #[test]
652    fn equality_operator_compares_fields() {
653        let s = render();
654        // ReliabilityQosPolicy compares both fields:
655        assert!(s.contains("kind_ == rhs.kind_ && max_blocking_time_ == rhs.max_blocking_time_"));
656        assert!(s.contains("bool operator!=(const ReliabilityQosPolicy& rhs)"));
657    }
658
659    #[test]
660    fn entity_factory_default_is_true() {
661        let s = render();
662        assert!(s.contains("class EntityFactoryQosPolicy {"));
663        assert!(s.contains("bool autoenable_created_entities_{true}"));
664    }
665
666    #[test]
667    fn ownership_strength_default_is_zero() {
668        let s = render();
669        assert!(s.contains("class OwnershipStrengthQosPolicy {"));
670        assert!(s.contains("int32_t value_{0}"));
671    }
672
673    #[test]
674    fn liveliness_qos_policy_has_kind_and_lease() {
675        let s = render();
676        assert!(s.contains("class LivelinessQosPolicy {"));
677        assert!(s.contains("LivelinessKind kind_"));
678        assert!(s.contains("Duration lease_duration_"));
679    }
680}