Skip to main content

ns_keyed_archive/
encode.rs

1/// Jackson Coxson
2use nskeyedarchiver_converter::ConverterError;
3use plist::{Dictionary, Uid, Value};
4
5const ARCHIVER: &str = "NSKeyedArchiver";
6const ARCHIVER_VERSION: u64 = 100000;
7
8const ARCHIVER_KEY_NAME: &str = "$archiver";
9const TOP_KEY_NAME: &str = "$top";
10const OBJECTS_KEY_NAME: &str = "$objects";
11const VERSION_KEY_NAME: &str = "$version";
12const NULL_OBJECT_REFERENCE_NAME: &str = "$null";
13
14/// Encodes a plist Value into NSKeyedArchiver format.
15///
16/// If successful, returns a plist::Value representing an NSKeyedArchiver encoded plist.
17pub fn encode(value: Value) -> Result<Value, ConverterError> {
18    // Initialize the objects array with $null as the first element (index 0)
19    let mut objects = vec![Value::String(NULL_OBJECT_REFERENCE_NAME.to_string())];
20
21    // Create the top-level structure
22    let mut root_dict = Dictionary::new();
23
24    // Add required headers
25    root_dict.insert(
26        ARCHIVER_KEY_NAME.to_string(),
27        Value::String(ARCHIVER.to_string()),
28    );
29    root_dict.insert(
30        VERSION_KEY_NAME.to_string(),
31        Value::Integer(ARCHIVER_VERSION.into()),
32    );
33
34    // Process the root value - it might be any kind of value, but we wrap it in "root"
35    let mut top_dict = Dictionary::new();
36    let uid = encode_object(value, &mut objects)?;
37    top_dict.insert("root".to_string(), Value::Uid(uid));
38
39    // Add top and objects to the root dictionary
40    root_dict.insert(TOP_KEY_NAME.to_string(), Value::Dictionary(top_dict));
41    root_dict.insert(OBJECTS_KEY_NAME.to_string(), Value::Array(objects));
42
43    Ok(Value::Dictionary(root_dict))
44}
45
46/// Encodes a single object and returns its UID
47fn encode_object(value: Value, objects: &mut Vec<Value>) -> Result<Uid, ConverterError> {
48    match value {
49        Value::Dictionary(dict) => encode_dictionary(dict, objects),
50        Value::Array(array) => encode_array(array, objects),
51        Value::Boolean(b) => {
52            // Add the boolean to objects array
53            objects.push(Value::Boolean(b));
54            Ok(Uid::new(objects.len() as u64 - 1))
55        }
56        Value::Real(r) => {
57            objects.push(Value::Real(r));
58            Ok(Uid::new(objects.len() as u64 - 1))
59        }
60        Value::Integer(i) => {
61            objects.push(Value::Integer(i));
62            Ok(Uid::new(objects.len() as u64 - 1))
63        }
64        Value::String(s) => {
65            if s == NULL_OBJECT_REFERENCE_NAME {
66                return Ok(Uid::new(0)); // Reference to $null
67            }
68            objects.push(Value::String(s));
69            Ok(Uid::new(objects.len() as u64 - 1))
70        }
71        Value::Date(d) => {
72            objects.push(Value::Date(d));
73            Ok(Uid::new(objects.len() as u64 - 1))
74        }
75        Value::Data(d) => {
76            objects.push(Value::Data(d));
77            Ok(Uid::new(objects.len() as u64 - 1))
78        }
79        Value::Uid(_) => {
80            // UIDs should be handled differently in the context they appear
81            Err(ConverterError::InvalidObjectEncoding(0))
82        }
83        _ => unimplemented!(),
84    }
85}
86
87/// Encodes a dictionary as an NSDictionary
88fn encode_dictionary(dict: Dictionary, objects: &mut Vec<Value>) -> Result<Uid, ConverterError> {
89    // Create arrays for keys and values
90    let mut key_uids = Vec::new();
91    let mut value_uids = Vec::new();
92
93    // Process all key-value pairs
94    for (key, value) in dict {
95        // First encode the key (usually a string)
96        let key_uid = encode_object(Value::String(key), objects)?;
97        key_uids.push(Value::Uid(key_uid));
98
99        // Then encode the value
100        let value_uid = encode_object(value, objects)?;
101        value_uids.push(Value::Uid(value_uid));
102    }
103
104    // Get or create the NSDictionary class reference
105    let class_uid = create_class_reference("NSDictionary", objects)?;
106
107    // Build the NSKeyedArchiver structure for a dictionary
108    let mut dict_structure = Dictionary::new();
109    dict_structure.insert("$class".to_string(), Value::Uid(class_uid));
110    dict_structure.insert("NS.keys".to_string(), Value::Array(key_uids));
111    dict_structure.insert("NS.objects".to_string(), Value::Array(value_uids));
112
113    // Add the structured dictionary to objects and return its UID
114    objects.push(Value::Dictionary(dict_structure));
115    Ok(Uid::new(objects.len() as u64 - 1))
116}
117
118/// Encodes an array as an NSArray
119fn encode_array(array: Vec<Value>, objects: &mut Vec<Value>) -> Result<Uid, ConverterError> {
120    // Encode array elements
121    let mut object_uids = Vec::new();
122
123    for item in array {
124        let item_uid = encode_object(item, objects)?;
125        object_uids.push(Value::Uid(item_uid));
126    }
127
128    // Create class reference for NSArray
129    let class_uid = create_class_reference("NSArray", objects)?;
130
131    // Create array structure
132    let mut array_structure = Dictionary::new();
133    array_structure.insert("$class".to_string(), Value::Uid(class_uid));
134    array_structure.insert("NS.objects".to_string(), Value::Array(object_uids));
135
136    // Add to objects array
137    objects.push(Value::Dictionary(array_structure));
138    Ok(Uid::new(objects.len() as u64 - 1))
139}
140
141/// Creates a class reference dictionary and returns its UID
142fn create_class_reference(
143    class_name: &str,
144    objects: &mut Vec<Value>,
145) -> Result<Uid, ConverterError> {
146    // Check if we already have this class reference
147    for (i, obj) in objects.iter().enumerate() {
148        if let Some(dict) = obj.as_dictionary()
149            && let Some(classes) = dict.get("$classes").and_then(|c| c.as_array())
150            && let Some(name) = classes.first().and_then(|n| n.as_string())
151            && name == class_name
152        {
153            return Ok(Uid::new(i as u64));
154        }
155    }
156
157    // Create class definition
158    let mut class_dict = Dictionary::new();
159    class_dict.insert(
160        "$classes".to_string(),
161        Value::Array(vec![Value::String(class_name.to_string())]),
162    );
163    class_dict.insert(
164        "$classname".to_string(),
165        Value::String(class_name.to_string()),
166    );
167
168    // Add to objects array
169    objects.push(Value::Dictionary(class_dict));
170    Ok(Uid::new(objects.len() as u64 - 1))
171}
172
173/// Encodes a Value to NSKeyedArchiver format and writes it to a file
174pub fn encode_to_file<P: AsRef<std::path::Path>>(
175    value: Value,
176    path: P,
177) -> Result<(), ConverterError> {
178    let encoded = encode(value)?;
179    encoded.to_file_binary(path)?;
180    Ok(())
181}
182
183/// Encodes a Value to NSKeyedArchiver format and returns it as bytes
184pub fn encode_to_bytes(value: Value) -> Result<Vec<u8>, ConverterError> {
185    let encoded = encode(value)?;
186
187    let buf = Vec::new();
188    let mut writer = std::io::BufWriter::new(buf);
189    plist::to_writer_binary(&mut writer, &encoded).unwrap();
190
191    Ok(writer.into_inner().unwrap())
192}
193
194/// Encodes a Value to NSKeyedArchiver format and writes it to a writer
195pub fn encode_to_writer<W: std::io::Write>(value: Value, writer: W) -> Result<(), ConverterError> {
196    let encoded = encode(value)?;
197    plist::to_writer_binary(writer, &encoded)?;
198    Ok(())
199}