Skip to main content

ember_plus/codec/
encoder.rs

1//! Glow encoder - converts Glow structures to BER bytes.
2
3use crate::error::Result;
4use crate::ber::{BerWriter, Tag, TagClass, TagType};
5use crate::glow::{
6    GlowRoot, GlowElement, GlowNode, GlowParameter, GlowFunction,
7    GlowMatrix, GlowConnection, GlowCommand, GlowTemplate,
8    InvocationResult, StreamEntry, EmberPath, EmberValue,
9};
10
11/// Encoder for Glow messages.
12pub struct GlowEncoder;
13
14impl GlowEncoder {
15    /// Encode a GlowRoot to BER bytes.
16    pub fn encode(root: &GlowRoot) -> Result<Vec<u8>> {
17        let mut writer = BerWriter::new();
18        Self::encode_root(&mut writer, root)?;
19        Ok(writer.into_bytes())
20    }
21
22    /// Encode the root element.
23    fn encode_root(writer: &mut BerWriter, root: &GlowRoot) -> Result<()> {
24        writer.write_application(0, |w| {
25            // Encode root element collection
26            if !root.elements.is_empty() {
27                w.write_application(11, |w2| {
28                    for element in &root.elements {
29                        Self::encode_element(w2, element)?;
30                    }
31                    Ok(())
32                })?;
33            }
34
35            // Encode invocation results
36            for result in &root.invocation_results {
37                Self::encode_invocation_result(w, result)?;
38            }
39
40            // Encode stream entries
41            if !root.streams.is_empty() {
42                w.write_application(6, |w2| {
43                    for entry in &root.streams {
44                        Self::encode_stream_entry(w2, entry)?;
45                    }
46                    Ok(())
47                })?;
48            }
49
50            Ok(())
51        })
52    }
53
54    /// Encode an element.
55    fn encode_element(writer: &mut BerWriter, element: &GlowElement) -> Result<()> {
56        match element {
57            GlowElement::Node(node) => Self::encode_node(writer, node),
58            GlowElement::Parameter(param) => Self::encode_parameter(writer, param),
59            GlowElement::Function(func) => Self::encode_function(writer, func),
60            GlowElement::Matrix(matrix) => Self::encode_matrix(writer, matrix),
61            GlowElement::Command(cmd) => Self::encode_numbered_command(writer, cmd),
62            GlowElement::QualifiedNode(path, node) => Self::encode_qualified_node(writer, path, node),
63            GlowElement::QualifiedParameter(path, param) => Self::encode_qualified_parameter(writer, path, param),
64            GlowElement::QualifiedFunction(path, func) => Self::encode_qualified_function(writer, path, func),
65            GlowElement::QualifiedMatrix(path, matrix) => Self::encode_qualified_matrix(writer, path, matrix),
66            GlowElement::Template(template) => Self::encode_template(writer, template),
67        }
68    }
69
70    /// Encode a node.
71    fn encode_node(writer: &mut BerWriter, node: &GlowNode) -> Result<()> {
72        writer.write_application(3, |w| {
73            // Number [0]
74            w.write_context_integer(0, node.number as i64)?;
75
76            // Contents [1]
77            if node.identifier.is_some() || node.description.is_some() 
78                || node.is_root.is_some() || node.is_online.is_some() {
79                w.write_context(1, |w2| {
80                    Self::encode_node_contents(w2, node)
81                })?;
82            }
83
84            // Children [2]
85            if !node.children.is_empty() {
86                w.write_context(2, |w2| {
87                    w2.write_application(4, |w3| {
88                        for child in &node.children {
89                            Self::encode_element(w3, child)?;
90                        }
91                        Ok(())
92                    })
93                })?;
94            }
95
96            Ok(())
97        })
98    }
99
100    /// Encode node contents wrapped in a SET.
101    fn encode_node_contents(writer: &mut BerWriter, node: &GlowNode) -> Result<()> {
102        // Node contents must be wrapped in a SET (0x31)
103        writer.write_set(|w| {
104            if let Some(id) = &node.identifier {
105                Self::encode_context_utf8string(w, 0, id)?;
106            }
107            if let Some(desc) = &node.description {
108                Self::encode_context_utf8string(w, 1, desc)?;
109            }
110            if let Some(is_root) = node.is_root {
111                Self::encode_context_bool(w, 2, is_root)?;
112            }
113            if let Some(is_online) = node.is_online {
114                Self::encode_context_bool(w, 3, is_online)?;
115            }
116            if let Some(schema) = &node.schema_identifiers {
117                Self::encode_context_utf8string(w, 4, schema)?;
118            }
119            Ok(())
120        })
121    }
122
123    /// Encode a parameter.
124    fn encode_parameter(writer: &mut BerWriter, param: &GlowParameter) -> Result<()> {
125        writer.write_application(1, |w| {
126            // Number [0]
127            w.write_context_integer(0, param.number as i64)?;
128
129            // Contents [1]
130            w.write_context(1, |w2| {
131                Self::encode_parameter_contents(w2, param)
132            })?;
133
134            // Children [2]
135            if !param.children.is_empty() {
136                w.write_context(2, |w2| {
137                    w2.write_application(4, |w3| {
138                        for child in &param.children {
139                            Self::encode_element(w3, child)?;
140                        }
141                        Ok(())
142                    })
143                })?;
144            }
145
146            Ok(())
147        })
148    }
149
150    /// Encode parameter contents.
151    fn encode_parameter_contents(writer: &mut BerWriter, param: &GlowParameter) -> Result<()> {
152        if let Some(id) = &param.identifier {
153            Self::encode_context_string(writer, 0, id)?;
154        }
155        if let Some(desc) = &param.description {
156            Self::encode_context_string(writer, 1, desc)?;
157        }
158        if let Some(value) = &param.value {
159            Self::encode_context_value(writer, 2, value)?;
160        }
161        if let Some(min) = &param.minimum {
162            Self::encode_context_value(writer, 3, min)?;
163        }
164        if let Some(max) = &param.maximum {
165            Self::encode_context_value(writer, 4, max)?;
166        }
167        if let Some(access) = param.access {
168            writer.write_context_integer(5, access as i64)?;
169        }
170        if let Some(format) = &param.format {
171            Self::encode_context_string(writer, 6, format)?;
172        }
173        if let Some(enumeration) = &param.enumeration {
174            Self::encode_context_string(writer, 7, enumeration)?;
175        }
176        if let Some(factor) = param.factor {
177            writer.write_context_integer(8, factor as i64)?;
178        }
179        if let Some(is_online) = param.is_online {
180            Self::encode_context_bool(writer, 9, is_online)?;
181        }
182        if let Some(formula) = &param.formula {
183            Self::encode_context_string(writer, 10, formula)?;
184        }
185        if let Some(step) = param.step {
186            writer.write_context_integer(11, step as i64)?;
187        }
188        if let Some(default) = &param.default {
189            Self::encode_context_value(writer, 12, default)?;
190        }
191        if let Some(param_type) = param.parameter_type {
192            writer.write_context_integer(13, param_type as i64)?;
193        }
194        if let Some(stream_id) = param.stream_identifier {
195            writer.write_context_integer(14, stream_id as i64)?;
196        }
197        Ok(())
198    }
199
200    /// Encode a function.
201    fn encode_function(writer: &mut BerWriter, func: &GlowFunction) -> Result<()> {
202        writer.write_application(19, |w| {
203            w.write_context_integer(0, func.number as i64)?;
204
205            w.write_context(1, |w2| {
206                if let Some(id) = &func.identifier {
207                    Self::encode_context_string(w2, 0, id)?;
208                }
209                if let Some(desc) = &func.description {
210                    Self::encode_context_string(w2, 1, desc)?;
211                }
212                // Arguments and result tuples would go here
213                Ok(())
214            })?;
215
216            if !func.children.is_empty() {
217                w.write_context(2, |w2| {
218                    w2.write_application(4, |w3| {
219                        for child in &func.children {
220                            Self::encode_element(w3, child)?;
221                        }
222                        Ok(())
223                    })
224                })?;
225            }
226
227            Ok(())
228        })
229    }
230
231    /// Encode a matrix.
232    fn encode_matrix(writer: &mut BerWriter, matrix: &GlowMatrix) -> Result<()> {
233        writer.write_application(13, |w| {
234            w.write_context_integer(0, matrix.number as i64)?;
235
236            w.write_context(1, |w2| {
237                if let Some(id) = &matrix.identifier {
238                    Self::encode_context_string(w2, 0, id)?;
239                }
240                if let Some(desc) = &matrix.description {
241                    Self::encode_context_string(w2, 1, desc)?;
242                }
243                if let Some(mt) = matrix.matrix_type {
244                    w2.write_context_integer(2, mt as i64)?;
245                }
246                if let Some(am) = matrix.addressing_mode {
247                    w2.write_context_integer(3, am as i64)?;
248                }
249                if let Some(tc) = matrix.target_count {
250                    w2.write_context_integer(4, tc as i64)?;
251                }
252                if let Some(sc) = matrix.source_count {
253                    w2.write_context_integer(5, sc as i64)?;
254                }
255                Ok(())
256            })?;
257
258            // Encode connections if present
259            if !matrix.connections.is_empty() {
260                w.write_context(5, |w2| {
261                    for conn in &matrix.connections {
262                        Self::encode_connection(w2, conn)?;
263                    }
264                    Ok(())
265                })?;
266            }
267
268            Ok(())
269        })
270    }
271
272    /// Encode a connection.
273    fn encode_connection(writer: &mut BerWriter, conn: &GlowConnection) -> Result<()> {
274        writer.write_application(16, |w| {
275            w.write_context_integer(0, conn.target as i64)?;
276
277            if !conn.sources.is_empty() {
278                w.write_context(1, |w2| {
279                    for &src in &conn.sources {
280                        w2.write_integer(src as i64)?;
281                    }
282                    Ok(())
283                })?;
284            }
285
286            if let Some(op) = conn.operation {
287                w.write_context_integer(2, op as i64)?;
288            }
289
290            if let Some(disp) = conn.disposition {
291                w.write_context_integer(3, disp as i64)?;
292            }
293
294            Ok(())
295        })
296    }
297
298    /// Encode a command wrapped in a NumberedTreeNode (context [0]).
299    /// This is required when the command is at root level.
300    fn encode_numbered_command(writer: &mut BerWriter, cmd: &GlowCommand) -> Result<()> {
301        // Wrap in context [0] for NumberedTreeNode at root
302        writer.write_context(0, |w| {
303            Self::encode_command(w, cmd)
304        })
305    }
306
307    /// Encode a command (APPLICATION 2).
308    fn encode_command(writer: &mut BerWriter, cmd: &GlowCommand) -> Result<()> {
309        writer.write_application(2, |w| {
310            // Command number in context [0] with universal INTEGER inside
311            w.write_context(0, |w2| {
312                w2.write_integer(cmd.number() as i64)
313            })?;
314
315            match cmd {
316                GlowCommand::Invoke { invocation_id, arguments } => {
317                    // Invoke options in context[2] with APPLICATION 22 (Invocation)
318                    w.write_context(2, |w2| {
319                        w2.write_application(22, |w3| {
320                            // Invocation ID in context[0]
321                            w3.write_context(0, |w4| {
322                                w4.write_integer(*invocation_id as i64)
323                            })?;
324                            // Arguments in context[1] as SEQUENCE
325                            w3.write_context(1, |w4| {
326                                w4.write_sequence(|w5| {
327                                    for arg in arguments {
328                                        Self::encode_value(w5, arg)?;
329                                    }
330                                    Ok(())
331                                })
332                            })?;
333                            Ok(())
334                        })
335                    })?;
336                }
337                _ => {}
338            }
339
340            Ok(())
341        })
342    }
343
344    /// Encode a qualified node.
345    fn encode_qualified_node(writer: &mut BerWriter, path: &EmberPath, node: &GlowNode) -> Result<()> {
346        // Wrap in context [0] for NumberedTreeNode
347        writer.write_context(0, |w| {
348            w.write_application(10, |w2| {
349                Self::encode_context_path(w2, 0, path)?;
350
351                // Always include context[1] with node contents (even if minimal)
352                // Server expects this to be present
353                w2.write_context(1, |w3| {
354                    Self::encode_node_contents(w3, node)
355                })?;
356
357                if !node.children.is_empty() {
358                    w2.write_context(2, |w3| {
359                        w3.write_application(4, |w4| {
360                            for child in &node.children {
361                                Self::encode_element(w4, child)?;
362                            }
363                            Ok(())
364                        })
365                    })?;
366                }
367
368                Ok(())
369            })
370        })
371    }
372
373    /// Encode a qualified parameter.
374    fn encode_qualified_parameter(writer: &mut BerWriter, path: &EmberPath, param: &GlowParameter) -> Result<()> {
375        writer.write_application(9, |w| {
376            Self::encode_context_path(w, 0, path)?;
377
378            w.write_context(1, |w2| {
379                Self::encode_parameter_contents(w2, param)
380            })?;
381
382            Ok(())
383        })
384    }
385
386    /// Encode a qualified function.
387    fn encode_qualified_function(writer: &mut BerWriter, path: &EmberPath, func: &GlowFunction) -> Result<()> {
388        // Wrap in context [0] for NumberedTreeNode
389        writer.write_context(0, |w| {
390            w.write_application(20, |w2| {
391                Self::encode_context_path(w2, 0, path)?;
392
393                // Only write contents if there's something to write
394                if func.identifier.is_some() || func.description.is_some() {
395                    w2.write_context(1, |w3| {
396                        if let Some(id) = &func.identifier {
397                            Self::encode_context_string(w3, 0, id)?;
398                        }
399                        if let Some(desc) = &func.description {
400                            Self::encode_context_string(w3, 1, desc)?;
401                        }
402                        Ok(())
403                    })?;
404                }
405
406                // Encode children (commands) in context [2]
407                if !func.children.is_empty() {
408                    w2.write_context(2, |w3| {
409                        w3.write_application(4, |w4| {
410                            for child in &func.children {
411                                Self::encode_element(w4, child)?;
412                            }
413                            Ok(())
414                        })
415                    })?;
416                }
417
418                Ok(())
419            })
420        })
421    }
422
423    /// Encode a qualified matrix.
424    fn encode_qualified_matrix(writer: &mut BerWriter, path: &EmberPath, matrix: &GlowMatrix) -> Result<()> {
425        writer.write_application(17, |w| {
426            Self::encode_context_path(w, 0, path)?;
427
428            w.write_context(1, |w2| {
429                if let Some(id) = &matrix.identifier {
430                    Self::encode_context_string(w2, 0, id)?;
431                }
432                if let Some(desc) = &matrix.description {
433                    Self::encode_context_string(w2, 1, desc)?;
434                }
435                Ok(())
436            })?;
437
438            Ok(())
439        })
440    }
441
442    /// Encode a template.
443    fn encode_template(writer: &mut BerWriter, template: &GlowTemplate) -> Result<()> {
444        writer.write_application(24, |w| {
445            w.write_context_integer(0, template.number as i64)?;
446            if let Some(desc) = &template.description {
447                Self::encode_context_string(w, 2, desc)?;
448            }
449            Ok(())
450        })
451    }
452
453    /// Encode an invocation result.
454    fn encode_invocation_result(writer: &mut BerWriter, result: &InvocationResult) -> Result<()> {
455        writer.write_application(23, |w| {
456            w.write_context_integer(0, result.invocation_id as i64)?;
457            Self::encode_context_bool(w, 1, result.success)?;
458
459            if !result.result.is_empty() {
460                w.write_context(2, |w2| {
461                    for value in &result.result {
462                        Self::encode_value(w2, value)?;
463                    }
464                    Ok(())
465                })?;
466            }
467
468            Ok(())
469        })
470    }
471
472    /// Encode a stream entry.
473    fn encode_stream_entry(writer: &mut BerWriter, entry: &StreamEntry) -> Result<()> {
474        writer.write_application(5, |w| {
475            w.write_context_integer(0, entry.stream_identifier as i64)?;
476            Self::encode_context_value(w, 1, &entry.value)?;
477            Ok(())
478        })
479    }
480
481    /// Encode a path as a RELATIVE-OID in a context tag.
482    fn encode_context_path(writer: &mut BerWriter, tag: u32, path: &EmberPath) -> Result<()> {
483        let components: Vec<u32> = path.iter().map(|&n| n as u32).collect();
484        writer.write_context(tag, |w| {
485            w.write_relative_oid(&components)
486        })
487    }
488
489    /// Encode a string in a context tag (primitive, for backward compatibility).
490    fn encode_context_string(writer: &mut BerWriter, tag: u32, value: &str) -> Result<()> {
491        let tag_obj = Tag::new(TagClass::Context, TagType::Primitive, tag);
492        writer.write_tlv(&tag_obj, value.as_bytes());
493        Ok(())
494    }
495
496    /// Encode a UTF8String inside a constructed context tag.
497    /// This is the correct format for Ember+ node contents: context[tag] -> UTF8String
498    fn encode_context_utf8string(writer: &mut BerWriter, tag: u32, value: &str) -> Result<()> {
499        writer.write_context(tag, |w| {
500            w.write_utf8string(value);
501            Ok(())
502        })
503    }
504
505    /// Encode a boolean in a context tag.
506    fn encode_context_bool(writer: &mut BerWriter, tag: u32, value: bool) -> Result<()> {
507        let tag_obj = Tag::new(TagClass::Context, TagType::Primitive, tag);
508        writer.write_tlv(&tag_obj, &[if value { 0xFF } else { 0x00 }]);
509        Ok(())
510    }
511
512    /// Encode a value in a context tag.
513    fn encode_context_value(writer: &mut BerWriter, tag: u32, value: &EmberValue) -> Result<()> {
514        writer.write_context(tag, |w| {
515            Self::encode_value(w, value)
516        })
517    }
518
519    /// Encode a value.
520    fn encode_value(writer: &mut BerWriter, value: &EmberValue) -> Result<()> {
521        match value {
522            EmberValue::Integer(v) => writer.write_integer(*v),
523            EmberValue::Real(v) => writer.write_real(*v),
524            EmberValue::String(v) => writer.write_utf8_string(v),
525            EmberValue::Boolean(v) => writer.write_boolean(*v),
526            EmberValue::Octets(v) => writer.write_octet_string(v),
527            EmberValue::Null => writer.write_null(),
528        }
529    }
530}