use core::str;
use std::{
convert::TryFrom,
fmt::{Display, Write},
marker::PhantomData,
};
use serde::{
de::{
Deserialize, DeserializeSeed, Deserializer, Error, MapAccess, SeqAccess, Unexpected,
Visitor,
},
ser::{Serialize, SerializeSeq, SerializeStruct, SerializeTupleStruct, Serializer},
};
use static_assertions::assert_impl_all;
use crate::{
array_display_fmt, dict_display_fmt, signature_parser::SignatureParser, structure_display_fmt,
utils::*, Array, Basic, Dict, DynamicType, ObjectPath, OwnedValue, Signature, Str, Structure,
StructureBuilder, Type,
};
#[cfg(feature = "gvariant")]
use crate::{maybe_display_fmt, Maybe};
#[cfg(unix)]
use crate::Fd;
#[derive(Debug, Clone, PartialEq)]
pub enum Value<'a> {
U8(u8),
Bool(bool),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
F64(f64),
Str(Str<'a>),
Signature(Signature<'a>),
ObjectPath(ObjectPath<'a>),
Value(Box<Value<'a>>),
Array(Array<'a>),
Dict(Dict<'a, 'a>),
Structure(Structure<'a>),
#[cfg(feature = "gvariant")]
Maybe(Maybe<'a>),
#[cfg(unix)]
Fd(Fd),
}
assert_impl_all!(Value<'_>: Send, Sync, Unpin);
macro_rules! serialize_value {
($self:ident $serializer:ident.$method:ident $($first_arg:expr)*) => {
match $self {
Value::U8(value) => $serializer.$method($($first_arg,)* value),
Value::Bool(value) => $serializer.$method($($first_arg,)* value),
Value::I16(value) => $serializer.$method($($first_arg,)* value),
Value::U16(value) => $serializer.$method($($first_arg,)* value),
Value::I32(value) => $serializer.$method($($first_arg,)* value),
Value::U32(value) => $serializer.$method($($first_arg,)* value),
Value::I64(value) => $serializer.$method($($first_arg,)* value),
Value::U64(value) => $serializer.$method($($first_arg,)* value),
Value::F64(value) => $serializer.$method($($first_arg,)* value),
Value::Str(value) => $serializer.$method($($first_arg,)* value),
Value::Signature(value) => $serializer.$method($($first_arg,)* value),
Value::ObjectPath(value) => $serializer.$method($($first_arg,)* value),
Value::Value(value) => $serializer.$method($($first_arg,)* value),
Value::Array(value) => $serializer.$method($($first_arg,)* value),
Value::Dict(value) => $serializer.$method($($first_arg,)* value),
Value::Structure(value) => $serializer.$method($($first_arg,)* value),
#[cfg(feature = "gvariant")]
Value::Maybe(value) => $serializer.$method($($first_arg,)* value),
#[cfg(unix)]
Value::Fd(value) => $serializer.$method($($first_arg,)* value),
}
}
}
impl<'a> Value<'a> {
pub fn new<T>(value: T) -> Self
where
T: Into<Self> + DynamicType,
{
if value.dynamic_signature() == VARIANT_SIGNATURE_STR {
Self::Value(Box::new(value.into()))
} else {
value.into()
}
}
pub fn to_owned(&self) -> OwnedValue {
OwnedValue(match self {
Value::U8(v) => Value::U8(*v),
Value::Bool(v) => Value::Bool(*v),
Value::I16(v) => Value::I16(*v),
Value::U16(v) => Value::U16(*v),
Value::I32(v) => Value::I32(*v),
Value::U32(v) => Value::U32(*v),
Value::I64(v) => Value::I64(*v),
Value::U64(v) => Value::U64(*v),
Value::F64(v) => Value::F64(*v),
Value::Str(v) => Value::Str(v.to_owned()),
Value::Signature(v) => Value::Signature(v.to_owned()),
Value::ObjectPath(v) => Value::ObjectPath(v.to_owned()),
Value::Value(v) => {
let o = OwnedValue::from(&**v);
Value::Value(Box::new(o.into_inner()))
}
Value::Array(v) => Value::Array(v.to_owned()),
Value::Dict(v) => Value::Dict(v.to_owned()),
Value::Structure(v) => Value::Structure(v.to_owned()),
#[cfg(feature = "gvariant")]
Value::Maybe(v) => Value::Maybe(v.to_owned()),
#[cfg(unix)]
Value::Fd(v) => Value::Fd(*v),
})
}
pub fn value_signature(&self) -> Signature<'_> {
match self {
Value::U8(_) => u8::signature(),
Value::Bool(_) => bool::signature(),
Value::I16(_) => i16::signature(),
Value::U16(_) => u16::signature(),
Value::I32(_) => i32::signature(),
Value::U32(_) => u32::signature(),
Value::I64(_) => i64::signature(),
Value::U64(_) => u64::signature(),
Value::F64(_) => f64::signature(),
Value::Str(_) => <&str>::signature(),
Value::Signature(_) => Signature::signature(),
Value::ObjectPath(_) => ObjectPath::signature(),
Value::Value(_) => Signature::from_static_str_unchecked("v"),
Value::Array(value) => value.full_signature().clone(),
Value::Dict(value) => value.full_signature().clone(),
Value::Structure(value) => value.full_signature().clone(),
#[cfg(feature = "gvariant")]
Value::Maybe(value) => value.full_signature().clone(),
#[cfg(unix)]
Value::Fd(_) => Fd::signature(),
}
}
pub(crate) fn serialize_value_as_struct_field<S>(
&self,
name: &'static str,
serializer: &mut S,
) -> Result<(), S::Error>
where
S: SerializeStruct,
{
serialize_value!(self serializer.serialize_field name)
}
pub(crate) fn serialize_value_as_tuple_struct_field<S>(
&self,
serializer: &mut S,
) -> Result<(), S::Error>
where
S: SerializeTupleStruct,
{
serialize_value!(self serializer.serialize_field)
}
pub(crate) fn serialize_value_as_seq_element<S>(
&self,
serializer: &mut S,
) -> Result<(), S::Error>
where
S: SerializeSeq,
{
serialize_value!(self serializer.serialize_element)
}
#[cfg(feature = "gvariant")]
pub(crate) fn serialize_value_as_some<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_value!(self serializer.serialize_some)
}
pub fn downcast<T: ?Sized>(self) -> Option<T>
where
T: TryFrom<Value<'a>>,
{
if let Value::Value(v) = self {
T::try_from(*v).ok()
} else {
T::try_from(self).ok()
}
}
pub fn downcast_ref<T>(&'a self) -> Option<&'a T>
where
T: ?Sized,
&'a T: TryFrom<&'a Value<'a>>,
{
if let Value::Value(v) = self {
<&T>::try_from(v).ok()
} else {
<&T>::try_from(self).ok()
}
}
}
impl Display for Value<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
value_display_fmt(self, f, true)
}
}
pub(crate) fn value_display_fmt(
value: &Value<'_>,
f: &mut std::fmt::Formatter<'_>,
type_annotate: bool,
) -> std::fmt::Result {
match value {
Value::U8(num) => {
if type_annotate {
f.write_str("byte ")?;
}
write!(f, "0x{:02x}", num)
}
Value::Bool(boolean) => {
write!(f, "{}", boolean)
}
Value::I16(num) => {
if type_annotate {
f.write_str("int16 ")?;
}
write!(f, "{}", num)
}
Value::U16(num) => {
if type_annotate {
f.write_str("uint16 ")?;
}
write!(f, "{}", num)
}
Value::I32(num) => {
write!(f, "{}", num)
}
Value::U32(num) => {
if type_annotate {
f.write_str("uint32 ")?;
}
write!(f, "{}", num)
}
Value::I64(num) => {
if type_annotate {
f.write_str("int64 ")?;
}
write!(f, "{}", num)
}
Value::U64(num) => {
if type_annotate {
f.write_str("uint64 ")?;
}
write!(f, "{}", num)
}
Value::F64(num) => {
if num.fract() == 0. {
write!(f, "{}.", num)
} else {
write!(f, "{}", num)
}
}
Value::Str(string) => {
write!(f, "{:?}", string.as_str())
}
Value::Signature(val) => {
if type_annotate {
f.write_str("signature ")?;
}
write!(f, "{:?}", val.as_str())
}
Value::ObjectPath(val) => {
if type_annotate {
f.write_str("objectpath ")?;
}
write!(f, "{:?}", val.as_str())
}
Value::Value(child) => {
f.write_char('<')?;
value_display_fmt(child, f, true)?;
f.write_char('>')?;
Ok(())
}
Value::Array(array) => array_display_fmt(array, f, type_annotate),
Value::Dict(dict) => dict_display_fmt(dict, f, type_annotate),
Value::Structure(structure) => structure_display_fmt(structure, f, type_annotate),
#[cfg(feature = "gvariant")]
Value::Maybe(maybe) => maybe_display_fmt(maybe, f, type_annotate),
#[cfg(unix)]
Value::Fd(handle) => {
if type_annotate {
f.write_str("handle ")?;
}
write!(f, "{}", handle)
}
}
}
impl<'a> Serialize for Value<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut structure = serializer.serialize_struct("zvariant::Value", 2)?;
let signature = self.value_signature();
structure.serialize_field("zvariant::Value::Signature", &signature)?;
self.serialize_value_as_struct_field("zvariant::Value::Value", &mut structure)?;
structure.end()
}
}
impl<'de: 'a, 'a> Deserialize<'de> for Value<'a> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let visitor = ValueVisitor;
deserializer.deserialize_any(visitor)
}
}
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value<'de>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a Value")
}
fn visit_seq<V>(self, mut visitor: V) -> Result<Value<'de>, V::Error>
where
V: SeqAccess<'de>,
{
let signature = visitor.next_element::<Signature<'_>>()?.ok_or_else(|| {
Error::invalid_value(Unexpected::Other("nothing"), &"a Value signature")
})?;
let seed = ValueSeed::<Value<'_>> {
signature,
phantom: PhantomData,
};
visitor
.next_element_seed(seed)?
.ok_or_else(|| Error::invalid_value(Unexpected::Other("nothing"), &"a Value value"))
}
fn visit_map<V>(self, mut visitor: V) -> Result<Value<'de>, V::Error>
where
V: MapAccess<'de>,
{
let (_, signature) = visitor
.next_entry::<&str, Signature<'_>>()?
.ok_or_else(|| {
Error::invalid_value(Unexpected::Other("nothing"), &"a Value signature")
})?;
let _ = visitor.next_key::<&str>()?;
let seed = ValueSeed::<Value<'_>> {
signature,
phantom: PhantomData,
};
visitor.next_value_seed(seed)
}
}
pub(crate) struct SignatureSeed<'de> {
pub signature: Signature<'de>,
}
impl<'de> SignatureSeed<'de> {
pub(crate) fn visit_array<V>(self, mut visitor: V) -> Result<Array<'de>, V::Error>
where
V: SeqAccess<'de>,
{
let element_signature = self.signature.slice(1..);
let mut array = Array::new_full_signature(self.signature.clone());
while let Some(elem) = visitor.next_element_seed(ValueSeed::<Value<'_>> {
signature: element_signature.clone(),
phantom: PhantomData,
})? {
elem.value_signature();
array.append(elem).map_err(Error::custom)?;
}
Ok(array)
}
pub(crate) fn visit_struct<V>(self, mut visitor: V) -> Result<Structure<'de>, V::Error>
where
V: SeqAccess<'de>,
{
let mut i = 1;
let signature_end = self.signature.len() - 1;
let mut builder = StructureBuilder::new();
while i < signature_end {
let fields_signature = self.signature.slice(i..signature_end);
let parser = SignatureParser::new(fields_signature.clone());
let len = parser.next_signature().map_err(Error::custom)?.len();
let field_signature = fields_signature.slice(0..len);
i += field_signature.len();
if let Some(field) = visitor.next_element_seed(ValueSeed::<Value<'_>> {
signature: field_signature,
phantom: PhantomData,
})? {
builder = builder.append_field(field);
}
}
Ok(builder.build_with_signature(self.signature))
}
}
impl<'de, T> From<ValueSeed<'de, T>> for SignatureSeed<'de> {
fn from(seed: ValueSeed<'de, T>) -> Self {
SignatureSeed {
signature: seed.signature,
}
}
}
struct ValueSeed<'de, T> {
signature: Signature<'de>,
phantom: PhantomData<T>,
}
impl<'de, T> ValueSeed<'de, T>
where
T: Deserialize<'de>,
{
#[inline]
fn visit_array<V>(self, visitor: V) -> Result<Value<'de>, V::Error>
where
V: SeqAccess<'de>,
{
SignatureSeed::from(self)
.visit_array(visitor)
.map(Value::Array)
}
#[inline]
fn visit_struct<V>(self, visitor: V) -> Result<Value<'de>, V::Error>
where
V: SeqAccess<'de>,
{
SignatureSeed::from(self)
.visit_struct(visitor)
.map(Value::Structure)
}
#[inline]
fn visit_variant<V>(self, visitor: V) -> Result<Value<'de>, V::Error>
where
V: SeqAccess<'de>,
{
ValueVisitor
.visit_seq(visitor)
.map(|v| Value::Value(Box::new(v)))
}
}
macro_rules! value_seed_basic_method {
($name:ident, $type:ty) => {
#[inline]
fn $name<E>(self, value: $type) -> Result<Value<'de>, E>
where
E: serde::de::Error,
{
Ok(value.into())
}
};
}
macro_rules! value_seed_str_method {
($name:ident, $type:ty, $constructor:ident) => {
fn $name<E>(self, value: $type) -> Result<Value<'de>, E>
where
E: serde::de::Error,
{
match self.signature.as_str() {
<&str>::SIGNATURE_STR => Ok(Value::Str(Str::from(value))),
Signature::SIGNATURE_STR => Ok(Value::Signature(Signature::$constructor(value))),
ObjectPath::SIGNATURE_STR => Ok(Value::ObjectPath(ObjectPath::$constructor(value))),
_ => {
let expected = format!(
"`{}`, `{}` or `{}`",
<&str>::SIGNATURE_STR,
Signature::SIGNATURE_STR,
ObjectPath::SIGNATURE_STR,
);
Err(Error::invalid_type(
Unexpected::Str(self.signature.as_str()),
&expected.as_str(),
))
}
}
}
};
}
impl<'de, T> Visitor<'de> for ValueSeed<'de, T>
where
T: Deserialize<'de>,
{
type Value = Value<'de>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a Value value")
}
value_seed_basic_method!(visit_bool, bool);
value_seed_basic_method!(visit_i16, i16);
value_seed_basic_method!(visit_i64, i64);
value_seed_basic_method!(visit_u8, u8);
value_seed_basic_method!(visit_u16, u16);
value_seed_basic_method!(visit_u32, u32);
value_seed_basic_method!(visit_u64, u64);
value_seed_basic_method!(visit_f64, f64);
fn visit_i32<E>(self, value: i32) -> Result<Value<'de>, E>
where
E: serde::de::Error,
{
let v = match self.signature.as_bytes().first().ok_or_else(|| {
Error::invalid_value(
Unexpected::Other("nothing"),
&"i32 or fd signature character",
)
})? {
#[cfg(unix)]
b'h' => Fd::from(value).into(),
_ => value.into(),
};
Ok(v)
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<Value<'de>, E>
where
E: serde::de::Error,
{
self.visit_string(String::from(value))
}
value_seed_str_method!(visit_borrowed_str, &'de str, from_str_unchecked);
fn visit_seq<V>(self, visitor: V) -> Result<Value<'de>, V::Error>
where
V: SeqAccess<'de>,
{
match self.signature.as_bytes().first().ok_or_else(|| {
Error::invalid_value(
Unexpected::Other("nothing"),
&"Array or Struct signature character",
)
})? {
b'a' => self.visit_array(visitor),
b'(' => self.visit_struct(visitor),
b'v' => self.visit_variant(visitor),
b => Err(Error::invalid_value(
Unexpected::Char(*b as char),
&"a Value signature",
)),
}
}
fn visit_map<V>(self, mut visitor: V) -> Result<Value<'de>, V::Error>
where
V: MapAccess<'de>,
{
if self.signature.len() < 5 {
return Err(serde::de::Error::invalid_length(
self.signature.len(),
&">= 5 characters in dict entry signature",
));
}
let key_signature = self.signature.slice(2..3);
let signature_end = self.signature.len() - 1;
let value_signature = self.signature.slice(3..signature_end);
let mut dict = Dict::new_full_signature(self.signature.clone());
while let Some((key, value)) = visitor.next_entry_seed(
ValueSeed::<Value<'_>> {
signature: key_signature.clone(),
phantom: PhantomData,
},
ValueSeed::<Value<'_>> {
signature: value_signature.clone(),
phantom: PhantomData,
},
)? {
dict.append(key, value).map_err(Error::custom)?;
}
Ok(Value::Dict(dict))
}
#[cfg(feature = "gvariant")]
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let visitor = ValueSeed::<T> {
signature: self.signature.slice(1..),
phantom: PhantomData,
};
deserializer
.deserialize_any(visitor)
.map(|v| Value::Maybe(Maybe::just_full_signature(v, self.signature)))
}
#[cfg(not(feature = "gvariant"))]
fn visit_some<D>(self, _deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
panic!("`Maybe` type is only supported for GVariant format but it's disabled");
}
#[cfg(feature = "gvariant")]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
let value = Maybe::nothing_full_signature(self.signature);
Ok(Value::Maybe(value))
}
#[cfg(not(feature = "gvariant"))]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
panic!("`Maybe` type is only supported for GVariant format but it's disabled");
}
}
impl<'de, T> DeserializeSeed<'de> for ValueSeed<'de, T>
where
T: Deserialize<'de>,
{
type Value = Value<'de>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(self)
}
}
impl<'a> Type for Value<'a> {
fn signature() -> Signature<'static> {
Signature::from_static_str_unchecked(VARIANT_SIGNATURE_STR)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
#[test]
fn value_display() {
assert_eq!(
Value::new((
255_u8,
true,
-1_i16,
65535_u16,
-1,
1_u32,
-9223372036854775808_i64,
18446744073709551615_u64,
(-1., 1.0, 11000000000., 1.1e-10)
))
.to_string(),
"(byte 0xff, true, int16 -1, uint16 65535, -1, uint32 1, \
int64 -9223372036854775808, uint64 18446744073709551615, \
(-1., 1., 11000000000., 0.00000000011))"
);
assert_eq!(
Value::new(vec![
"", " ", "a", r#"""#, "'", "a'b", "a'\"b", "\\", "\n'\"",
])
.to_string(),
r#"["", " ", "a", "\"", "'", "a'b", "a'\"b", "\\", "\n'\""]"#
);
assert_eq!(
Value::new(vec![
"\x07\x08\x09\x0A\x0B\x0C\x0D",
"\x7F",
char::from_u32(0xD8000).unwrap().to_string().as_str()
])
.to_string(),
r#"["\u{7}\u{8}\t\n\u{b}\u{c}\r", "\u{7f}", "\u{d8000}"]"#
);
assert_eq!(
Value::new((
vec![
Signature::from_static_str("").unwrap(),
Signature::from_static_str("(ysa{sd})").unwrap(),
],
vec![
ObjectPath::from_static_str("/").unwrap(),
ObjectPath::from_static_str("/a/very/looooooooooooooooooooooooo0000o0ng/path")
.unwrap(),
],
vec![
Value::new(0_u8),
Value::new((Value::new(51), Value::new(Value::new(1_u32)))),
]
))
.to_string(),
"([signature \"\", \"(ysa{sd})\"], \
[objectpath \"/\", \"/a/very/looooooooooooooooooooooooo0000o0ng/path\"], \
[<byte 0x00>, <(<51>, <<uint32 1>>)>])"
);
assert_eq!(Value::new(vec![] as Vec<Vec<i64>>).to_string(), "@aax []");
assert_eq!(
Value::new(vec![
vec![0_i16, 1_i16],
vec![2_i16, 3_i16],
vec![4_i16, 5_i16]
])
.to_string(),
"[[int16 0, 1], [2, 3], [4, 5]]"
);
assert_eq!(
Value::new(vec![
b"Hello".to_vec(),
b"Hell\0o".to_vec(),
b"H\0ello\0".to_vec(),
b"Hello\0".to_vec(),
b"\0".to_vec(),
b" \0".to_vec(),
b"'\0".to_vec(),
b"\n'\"\0".to_vec(),
b"\\\0".to_vec(),
])
.to_string(),
"[[byte 0x48, 0x65, 0x6c, 0x6c, 0x6f], \
[0x48, 0x65, 0x6c, 0x6c, 0x00, 0x6f], \
[0x48, 0x00, 0x65, 0x6c, 0x6c, 0x6f, 0x00], \
b\"Hello\", b\"\", b\" \", b\"'\", b\"\\n'\\\"\", b\"\\\\\"]"
);
assert_eq!(
Value::new(HashMap::<bool, bool>::new()).to_string(),
"@a{bb} {}"
);
assert_eq!(
Value::new(vec![(true, 0_i64)].into_iter().collect::<HashMap<_, _>>()).to_string(),
"{true: int64 0}",
);
let val = Value::new(
vec![(32_u16, 64_i64), (100_u16, 200_i64)]
.into_iter()
.collect::<HashMap<_, _>>(),
)
.to_string();
assert!(val.starts_with('{'));
assert!(val.ends_with('}'));
assert_eq!(val.matches("uint16").count(), 1);
assert_eq!(val.matches("int64").count(), 1);
let items_str = val.split(", ").collect::<Vec<_>>();
assert_eq!(items_str.len(), 2);
assert!(items_str
.iter()
.any(|str| str.contains("32") && str.contains(": ") && str.contains("64")));
assert!(items_str
.iter()
.any(|str| str.contains("100") && str.contains(": ") && str.contains("200")));
assert_eq!(Value::new(Structure::default()).to_string(), "()");
assert_eq!(
Value::new(((true,), (true, false), (true, true, false))).to_string(),
"((true,), (true, false), (true, true, false))"
);
assert_eq!(
Value::new((
(Some(0_i16), Some(Some(0_i16)), Some(Some(Some(0_i16))),),
(None::<i16>, Some(None::<i16>), Some(Some(None::<i16>)),),
(None::<Option<i16>>, Some(None::<Option<i16>>)),
))
.to_string(),
"((@mn 0, @mmn 0, @mmmn 0), \
(@mn nothing, @mmn just nothing, @mmmn just just nothing), \
(@mmn nothing, @mmmn just nothing))"
);
assert_eq!(
Value::new(vec![Fd::from(0), Fd::from(-100)]).to_string(),
"[handle 0, -100]"
);
assert_eq!(
Value::new((
None::<bool>,
None::<bool>,
Some(
vec![("size", Value::new((800, 600)))]
.into_iter()
.collect::<HashMap<_, _>>()
),
vec![
Value::new(1),
Value::new(
vec![(
"dimension",
Value::new((
vec![2.4, 1.],
Some(Some(200_i16)),
Value::new((3_u8, "Hello!"))
))
)]
.into_iter()
.collect::<HashMap<_, _>>()
)
],
7777,
ObjectPath::from_static_str("/").unwrap(),
8888
))
.to_string(),
"(@mb nothing, @mb nothing, \
@ma{sv} {\"size\": <(800, 600)>}, \
[<1>, <{\"dimension\": <([2.4, 1.], \
@mmn 200, <(byte 0x03, \"Hello!\")>)>}>], \
7777, objectpath \"/\", 8888)"
);
}
}