metaverse_messages 0.3.0

packet definitions for the open metaverse
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
use std::{fmt::Display, time::SystemTime};

use serde::{Deserialize, Serialize};
use serde_llsd_benthic::LLSDValue;
use uuid::Uuid;

use super::object_types::ObjectType;

#[derive(Debug, Clone, Serialize, Deserialize)]
/// Metadata shared by all opensimulator items
/// this is received by the FetchInventory endpoint, and can be used to retrieve additional items
/// data from the ViewerAsset endpoint.
pub struct ItemMetadata {
    /// name of the item
    pub name: String,
    /// Permissions attached to the item. Who can create, copy, transfer, sell etc.
    pub permissions: Permissions,
    /// Sale information of the item. Is it for sale, how much, etc.
    pub sale_info: SaleInfo,
    /// The ID of the item, used for retrieving from the ViewerAsset endpoint
    pub asset_id: Uuid,
    /// The parent object. If the object is the root, the id is zeroed out.
    pub parent_id: Uuid,
    /// global ID of the item
    pub item_id: Uuid,
    /// Description of the object.
    pub description: String,
    /// Unix timestamp for when the object was created
    pub created_at: std::time::SystemTime,
    /// Name of the  object
    pub inventory_type: i32,
    /// item flags
    pub flags: i32,
    /// type of the item. Clothes, tree, animation, etc.
    pub item_type: ObjectType,
}
impl Default for ItemMetadata {
    fn default() -> Self {
        Self {
            name: Default::default(),
            sale_info: Default::default(),
            permissions: Default::default(),
            asset_id: Default::default(),
            parent_id: Default::default(),
            item_id: Default::default(),
            description: Default::default(),
            created_at: SystemTime::UNIX_EPOCH,
            inventory_type: 0,
            flags: 0,
            item_type: Default::default(),
        }
    }
}
impl ItemMetadata {
    /// Item metadata comes in through an unstructured newline seperated type when receiving item
    /// data from the ViewerAsset endpoint. This parses the item metadata as bytes.
    pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
        let text = std::str::from_utf8(bytes)
            .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid UTF-8"))?;
        let mut lines = text.lines().map(str::trim);
        let mut metadata = ItemMetadata::default();

        let mut next_line = || {
            lines.next().ok_or_else(|| {
                std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Unexpected end of data")
            })
        };

        let _version = next_line()?.to_owned();
        let name = next_line()?.to_owned();
        metadata.name = name;

        next_line()?; // permissions 0
        next_line()?; // {
        next_line()?; // base_mask

        let parse_hex = |line: &str| {
            i32::from_str_radix(
                line.split('\t').nth(1).ok_or_else(|| {
                    std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing hex field")
                })?,
                16,
            )
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
        };

        let base_mask = parse_hex(next_line()?)?;
        let owner_mask = parse_hex(next_line()?)?;
        let group_mask = parse_hex(next_line()?)?;
        let everyone_mask = parse_hex(next_line()?)?;
        let next_owner_mask = parse_hex(next_line()?)?;

        let parse_uuid = |line: &str| {
            let id_str = line.split('\t').nth(1).ok_or_else(|| {
                std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing UUID field")
            })?;
            Uuid::parse_str(id_str)
                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
        };

        let creator_id = parse_uuid(next_line()?)?;
        let owner_id = parse_uuid(next_line()?)?;
        let last_owner_id = Some(parse_uuid(next_line()?)?);
        let group_id = parse_uuid(next_line()?)?;

        let permissions = Permissions {
            base_mask,
            owner_mask,
            group_mask,
            everyone_mask,
            next_owner_mask,
            creator_id,
            last_owner_id,
            group_id,
            owner_id,
            is_owner_group: None,
        };
        metadata.permissions = permissions;

        next_line()?; // }
        next_line()?; // sale_info 0
        next_line()?; // {
        next_line()?; // sale_type

        let price = {
            let price_str = next_line()?.split('\t').nth(1).ok_or_else(|| {
                std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing sale price")
            })?;
            parse_or_io(price_str)?
        };

        metadata.sale_info.price = price;

        Ok(metadata)
    }
    /// When receiving item metadata from the FetchInventoryDescendents endpoint. item metadadata
    /// is received in LLSD format.
    pub fn from_llsd(data: &LLSDValue) -> std::io::Result<Self> {
        if let Some(item) = data.as_map() {
            let asset_id = match item.get("asset_id") {
                Some(LLSDValue::UUID(asset_id)) => *asset_id,
                _ => Uuid::nil(),
            };
            let parent_id = match item.get("parent_id") {
                Some(LLSDValue::UUID(parent_id)) => *parent_id,
                _ => Uuid::nil(),
            };
            let item_id = match item.get("item_id") {
                Some(LLSDValue::UUID(item_id)) => *item_id,
                _ => Uuid::nil(),
            };

            let sale_info = match item.get("sale_info") {
                Some(sale) => SaleInfo::from_llsd(sale)?,
                _ => SaleInfo {
                    ..Default::default()
                },
            };

            let permissions = match item.get("permissions") {
                Some(permission) => Permissions::from_llsd(permission)?,
                _ => Permissions {
                    ..Default::default()
                },
            };

            let description = match item.get("desc") {
                Some(LLSDValue::String(description)) => description.to_string(),
                _ => "".to_string(),
            };
            let created_at = match item.get("created_at") {
                Some(LLSDValue::Integer(created_at)) => {
                    SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(*created_at as u64)
                }
                _ => SystemTime::now(),
            };
            let name = match item.get("name") {
                Some(LLSDValue::String(name)) => name.to_string(),
                _ => "".to_string(),
            };
            let inventory_type = match item.get("inv_type") {
                Some(LLSDValue::Integer(asset_id)) => *asset_id,
                _ => 0,
            };
            let flags = match item.get("flags") {
                Some(LLSDValue::Integer(flags)) => *flags,
                _ => 0,
            };
            let item_type = match item.get("type") {
                Some(LLSDValue::Integer(item_type)) => {
                    ObjectType::from_bytes(&if *item_type >= 0 {
                        *item_type as u8
                    } else {
                        println!("WEIRD TYPE GOT {:?}", item_type);
                        99
                    })
                }
                _ => ObjectType::Unknown,
            };
            Ok(ItemMetadata {
                name,
                permissions,
                sale_info,
                asset_id,
                item_id,
                created_at,
                description,
                inventory_type,
                item_type,
                flags,
                parent_id,
            })
        } else {
            Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Missing or invalid item metadata",
            ))
        }
    }
}
fn parse_or_io<T: std::str::FromStr>(s: &str) -> std::io::Result<T>
where
    T::Err: std::fmt::Display,
{
    s.parse::<T>()
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// Information regarding the permissions the object has
pub struct Permissions {
    /// The UUID of the current owner of the object
    pub owner_id: Uuid,
    /// The group that the object belongs to. This allows permissions for objects to be handled on
    /// a user group level instead of an individual level.
    pub group_id: Uuid,
    /// The ID of the creator of the object
    pub creator_id: Uuid,
    /// The default permissions of the object that apply regardless of ownership or group.
    pub base_mask: i32,
    /// Permissions granted to all users. Broadest level of access.
    pub everyone_mask: i32,
    /// Permissions granted to users who belong to the specified group ID.
    pub group_mask: i32,
    /// Permissions granted to the next owner of the object, when it is transferred.
    pub next_owner_mask: i32,
    /// Permissions the current owner has on the object.
    pub owner_mask: i32,
    /// indicates if the owner is the group istelf, rather than an individual user
    /// TODO: remove the option here. Make sure that this isn't acutally optional
    pub is_owner_group: Option<bool>,
    /// Stores the UUID of the last owner
    /// TODO: Remove the option here. Make sure that this isn't actually optional
    pub last_owner_id: Option<Uuid>,
}
impl Permissions {
    /// convert LLSD values to permissions
    pub fn from_llsd(data: &LLSDValue) -> std::io::Result<Self> {
        if let Some(permissions) = data.as_map() {
            let owner_id = match permissions.get("owner_id") {
                Some(LLSDValue::UUID(owner_id)) => *owner_id,
                _ => Uuid::nil(),
            };
            let group_id = match permissions.get("group_id") {
                Some(LLSDValue::UUID(group_id)) => *group_id,
                _ => Uuid::nil(),
            };
            let creator_id = match permissions.get("creator_id") {
                Some(LLSDValue::UUID(creator_id)) => *creator_id,
                _ => Uuid::nil(),
            };
            let base_mask = match permissions.get("base_mask") {
                Some(LLSDValue::Integer(base_mask)) => *base_mask,
                _ => 0,
            };
            let everyone_mask = match permissions.get("everyone_mask") {
                Some(LLSDValue::Integer(everyone_mask)) => *everyone_mask,
                _ => 0,
            };
            let group_mask = match permissions.get("group_mask") {
                Some(LLSDValue::Integer(group_mask)) => *group_mask,
                _ => 0,
            };
            let next_owner_mask = match permissions.get("next_owner_mask") {
                Some(LLSDValue::Integer(next_owner_mask)) => *next_owner_mask,
                _ => 0,
            };
            let owner_mask = match permissions.get("owner_mask") {
                Some(LLSDValue::Integer(owner_mask)) => *owner_mask,
                _ => 0,
            };
            let is_owner_group = match permissions.get("is_owner_group") {
                Some(LLSDValue::Boolean(is_owner_group)) => Some(*is_owner_group),
                _ => None,
            };
            Ok(Permissions {
                owner_id,
                group_id,
                creator_id,
                base_mask,
                everyone_mask,
                group_mask,
                is_owner_group,
                next_owner_mask,
                owner_mask,
                last_owner_id: None,
            })
        } else {
            Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Missing or invalid image permission data",
            ))
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// information regarding the sale of the object. How it was sold and how much it costs.
pub struct SaleInfo {
    /// The type of sale
    pub sale_type: SaleType,
    /// The price of the object
    pub price: i32,
    /// Recurring cost that is associated with owning the object after the purchase.
    /// Functionally how much you are renting it for.
    pub ownership_cost: Option<i32>,
}
impl SaleInfo {
    /// Converts LLSD to a SaleInfo object
    pub fn from_llsd(data: &LLSDValue) -> std::io::Result<Self> {
        if let Some(sale_info) = data.as_map() {
            let sale_type = match sale_info.get("sale_type") {
                Some(LLSDValue::Integer(sale_type)) => SaleType::from_i32(*sale_type),
                _ => {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        "Missing or invalid sale type",
                    ));
                }
            };
            let price = match sale_info.get("sale_price") {
                Some(LLSDValue::Integer(price)) => *price,
                _ => {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        "Missing or invalid price",
                    ));
                }
            };
            Ok(SaleInfo {
                sale_type,
                price,
                ownership_cost: None,
            })
        } else {
            Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Missing or invalid image sale data",
            ))
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// Values to determine what the for sale state of an object is.
pub enum SaleType {
    /// Not for sale
    Not,
    /// The original item is for sale.
    Original,
    /// A copy of the original is for sale
    Copy,
    /// The contents inside the object are for sale
    Contents,

    #[default]
    /// unknown
    Unknown,
}

impl Display for SaleType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Not => write!(f, "Not"),
            Self::Original => write!(f, "Original"),
            Self::Copy => write!(f, "Copy"),
            Self::Contents => write!(f, "Contents"),
            Self::Unknown => write!(f, "Unknown"),
        }
    }
}

impl SaleType {
    /// Convert the SaleType from an i32
    pub fn from_i32(byte: i32) -> Self {
        match byte {
            0 => Self::Not,
            1 => Self::Original,
            2 => Self::Copy,
            3 => Self::Contents,
            _ => Self::Unknown,
        }
    }
    /// Convert the SaleType from a string
    pub fn from_string(str: &str) -> Self {
        match str {
            "not" | "Not" => SaleType::Not,
            "original" | "Original" => SaleType::Original,
            "copy" | "Copy" => SaleType::Copy,
            "contents" | "Contents" => SaleType::Contents,
            _ => SaleType::Unknown,
        }
    }
}