1use crate::util::msgpack_read_rest_of_str;
2use crate::util::msgpack_read_str;
3use crate::util::DisplayAsHexBytesLimitted;
4use crate::util::DisplayErrorLocation;
5use crate::util::FfiSafeBytes;
6use crate::util::FfiSafeStr;
7use std::borrow::Cow;
8use std::cell::OnceCell;
9use std::cmp::Ordering;
10use std::collections::HashMap;
11use std::io::Cursor;
12use tarantool::error::BoxError;
13use tarantool::error::TarantoolErrorCode;
14use tarantool::error::TarantoolErrorCode::InvalidMsgpack;
15use tarantool::ffi::uuid::tt_uuid;
16use tarantool::msgpack::skip_value;
17use tarantool::unwrap_ok_or;
18use tarantool::uuid::Uuid;
19
20pub struct Context<'a> {
33 request_id: Uuid,
34 path: &'a str,
35 plugin_name: &'a str,
36 service_name: &'a str,
37 plugin_version: &'a str,
38
39 raw: &'a [u8],
41
42 named_fields: OnceCell<Result<ContextNamedFields<'a>, BoxError>>,
44}
45
46type ContextNamedFields<'a> = HashMap<Cow<'a, str>, ContextValue<'a>>;
47
48impl<'a> Context<'a> {
49 #[inline]
50 pub(crate) fn new(context: &'a FfiSafeContext) -> Self {
51 let path = unsafe { context.path.as_str() };
53 let plugin_name = unsafe { context.plugin_name.as_str() };
54 let service_name = unsafe { context.service_name.as_str() };
55 let plugin_version = unsafe { context.plugin_version.as_str() };
56 let raw = unsafe { context.raw.as_bytes() };
57 Self {
58 request_id: Uuid::from_tt_uuid(context.request_id),
59 path,
60 plugin_name,
61 service_name,
62 plugin_version,
63 raw,
64 named_fields: OnceCell::new(),
65 }
66 }
67
68 #[inline(always)]
69 pub fn request_id(&self) -> Uuid {
70 self.request_id
71 }
72
73 #[inline(always)]
74 pub fn path(&self) -> &str {
75 self.path
76 }
77
78 #[inline(always)]
79 pub fn plugin_name(&self) -> &str {
80 self.plugin_name
81 }
82
83 #[inline(always)]
84 pub fn service_name(&self) -> &str {
85 self.service_name
86 }
87
88 #[inline(always)]
89 pub fn plugin_version(&self) -> &str {
90 self.plugin_version
91 }
92
93 #[inline(always)]
94 pub fn raw(&self) -> &[u8] {
95 self.raw
96 }
97
98 #[inline(always)]
100 pub fn set(
101 &mut self,
102 field: impl Into<Cow<'static, str>>,
103 value: impl Into<ContextValue<'static>>,
104 ) -> Result<Option<ContextValue<'a>>, BoxError> {
105 self.get_named_fields()?;
108
109 let res: &mut Result<ContextNamedFields, BoxError> = self
110 .named_fields
111 .get_mut()
112 .expect("just made sure it's there");
113 let named_fields: &mut ContextNamedFields = res
114 .as_mut()
115 .expect("if it was an error we would've returned early");
116 let old_value = named_fields.insert(field.into(), value.into());
117 Ok(old_value)
118 }
119
120 #[inline(always)]
121 pub fn get(&self, field: &str) -> Result<Option<&ContextValue<'a>>, BoxError> {
122 let named_fields = self.get_named_fields()?;
123 Ok(named_fields.get(field))
124 }
125
126 #[inline]
127 pub fn get_named_fields(&self) -> Result<&ContextNamedFields<'a>, BoxError> {
128 self.named_fields
129 .get_or_init(|| decode_msgpack_string_fields(self.raw))
130 .as_ref()
131 .map_err(Clone::clone)
132 }
133}
134
135impl std::fmt::Debug for Context<'_> {
136 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
137 let mut s = f.debug_struct("Context");
138 let mut s = s
139 .field("request_id", &DebugUuid(self.request_id))
140 .field("path", &self.path)
141 .field("plugin_name", &self.plugin_name)
142 .field("service_name", &self.service_name)
143 .field("plugin_version", &self.plugin_version);
144 if let Ok(named_fields) = self.get_named_fields() {
145 for (key, value) in named_fields {
146 s = s.field(key, value);
147 }
148 s.finish()
149 } else {
150 s.finish_non_exhaustive()
151 }
152 }
153}
154
155tarantool::define_enum_with_introspection! {
156 pub enum ContextFieldId {
158 RequestId = 1,
159 PluginName = 2,
160 ServiceName = 3,
161 PluginVersion = 4,
162 }
163}
164
165#[inline]
166fn decode_msgpack_string_fields(raw: &[u8]) -> Result<ContextNamedFields<'_>, BoxError> {
167 decode_msgpack_string_fields_impl(raw).map_err(|e| log_context_decoding_error(raw, e))
168}
169
170fn decode_msgpack_string_fields_impl(raw: &[u8]) -> Result<ContextNamedFields<'_>, BoxError> {
171 let mut buffer = Cursor::new(raw);
172
173 let count = rmp::decode::read_map_len(&mut buffer).map_err(invalid_msgpack)? as usize;
174
175 let mut named_fields = ContextNamedFields::with_capacity(count);
176 for i in 0..count {
177 let Ok(marker) = rmp::decode::read_marker(&mut buffer) else {
178 #[rustfmt::skip]
179 return Err(BoxError::new(InvalidMsgpack, format!("not enough values in mapping: expected {count}, got {i}")));
180 };
181 if let Some(_field_id) = read_rest_of_uint(marker, &mut buffer)? {
182 skip_value(&mut buffer).map_err(invalid_msgpack)?;
185 } else if let Some(field_name) = msgpack_read_rest_of_str(marker, &mut buffer)? {
186 let value = rmp_serde::decode::from_read(&mut buffer)
188 .map_err(|e| decoding_field(field_name, invalid_msgpack(e)))?;
189 named_fields.insert(Cow::Borrowed(field_name), value);
190 } else {
191 #[rustfmt::skip]
193 return Err(BoxError::new(InvalidMsgpack, format!("context field must be positive integer or string, got {marker:?}")));
194 }
195 }
196
197 Ok(named_fields)
198}
199
200#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
205#[serde(untagged)]
206pub enum ContextValue<'a> {
207 Bool(bool),
208 Int(i64),
209 Float(f64),
210 String(Cow<'a, str>),
211 Array(Vec<ContextValue<'a>>),
212}
213
214impl ContextValue<'_> {
215 #[inline(always)]
216 pub fn bool(&self) -> Option<bool> {
217 match self {
218 &Self::Bool(v) => Some(v),
219 _ => None,
220 }
221 }
222
223 #[inline(always)]
224 pub fn int(&self) -> Option<i64> {
225 match self {
226 &Self::Int(v) => Some(v),
227 _ => None,
228 }
229 }
230
231 #[inline(always)]
232 pub fn float(&self) -> Option<f64> {
233 match *self {
234 Self::Float(v) => Some(v),
235 Self::Int(v) => Some(v as _),
236 _ => None,
237 }
238 }
239
240 #[inline(always)]
241 pub fn str(&self) -> Option<&str> {
242 match self {
243 Self::String(v) => Some(v),
244 _ => None,
245 }
246 }
247
248 #[inline(always)]
249 pub fn array(&self) -> Option<&[Self]> {
250 match self {
251 Self::Array(v) => Some(v),
252 _ => None,
253 }
254 }
255
256 pub fn is_owned(&self) -> bool {
260 match self {
261 Self::Bool { .. } => true,
262 Self::Int { .. } => true,
263 Self::Float { .. } => true,
264 Self::String(v) => matches!(v, Cow::Owned { .. }),
265 Self::Array(v) => v.iter().all(Self::is_owned),
266 }
267 }
268
269 pub fn into_owned(self) -> ContextValue<'static> {
272 match self {
273 Self::Bool(v) => ContextValue::Bool(v),
274 Self::Int(v) => ContextValue::Int(v),
275 Self::Float(v) => ContextValue::Float(v),
276 Self::String(v) => ContextValue::String(Cow::Owned(v.into())),
280 Self::Array(v) => ContextValue::Array(v.into_iter().map(Self::into_owned).collect()),
281 }
282 }
283}
284
285impl From<bool> for ContextValue<'_> {
286 #[inline(always)]
287 fn from(v: bool) -> Self {
288 Self::Bool(v)
289 }
290}
291
292#[rustfmt::skip]
293pub mod impl_int {
294 use super::*;
295 impl From<i8> for ContextValue<'_> { #[inline(always)] fn from(v: i8) -> Self { Self::Int(v as _) } }
296 impl From<i16> for ContextValue<'_> { #[inline(always)] fn from(v: i16) -> Self { Self::Int(v as _) } }
297 impl From<i32> for ContextValue<'_> { #[inline(always)] fn from(v: i32) -> Self { Self::Int(v as _) } }
298 impl From<i64> for ContextValue<'_> { #[inline(always)] fn from(v: i64) -> Self { Self::Int(v as _) } }
299 impl From<isize> for ContextValue<'_> { #[inline(always)] fn from(v: isize) -> Self { Self::Int(v as _) } }
300 impl From<u8> for ContextValue<'_> { #[inline(always)] fn from(v: u8) -> Self { Self::Int(v as _) } }
301 impl From<u16> for ContextValue<'_> { #[inline(always)] fn from(v: u16) -> Self { Self::Int(v as _) } }
302 impl From<u32> for ContextValue<'_> { #[inline(always)] fn from(v: u32) -> Self { Self::Int(v as _) } }
303 impl From<u64> for ContextValue<'_> { #[inline(always)] fn from(v: u64) -> Self { Self::Int(v as _) } }
304 impl From<usize> for ContextValue<'_> { #[inline(always)] fn from(v: usize) -> Self { Self::Int(v as _) } }
305}
306
307impl From<f32> for ContextValue<'_> {
308 #[inline(always)]
309 fn from(v: f32) -> Self {
310 Self::Float(v as _)
311 }
312}
313
314impl From<f64> for ContextValue<'_> {
315 #[inline(always)]
316 fn from(v: f64) -> Self {
317 Self::Float(v)
318 }
319}
320
321impl<'a> From<&'a str> for ContextValue<'a> {
322 #[inline(always)]
323 fn from(s: &'a str) -> Self {
324 Self::String(s.into())
325 }
326}
327
328impl From<String> for ContextValue<'_> {
329 #[inline(always)]
330 fn from(s: String) -> Self {
331 Self::String(s.into())
332 }
333}
334
335impl<'a> From<Cow<'a, str>> for ContextValue<'a> {
336 #[inline(always)]
337 fn from(s: Cow<'a, str>) -> Self {
338 Self::String(s)
339 }
340}
341
342#[derive(Copy, Clone)]
350#[repr(C)]
351pub struct FfiSafeContext {
352 pub request_id: tt_uuid,
353 pub path: FfiSafeStr,
354 pub plugin_name: FfiSafeStr,
355 pub service_name: FfiSafeStr,
356 pub plugin_version: FfiSafeStr,
357
358 raw: FfiSafeBytes,
360}
361
362impl FfiSafeContext {
363 #[inline]
364 pub fn for_local_call(
365 request_id: Uuid,
366 path: &str,
367 plugin: &str,
368 service: &str,
369 version: &str,
370 raw: &[u8],
371 ) -> Self {
372 Self {
373 request_id: request_id.to_tt_uuid(),
374 path: path.into(),
375 plugin_name: plugin.into(),
376 service_name: service.into(),
377 plugin_version: version.into(),
378
379 raw: raw.into(),
380 }
381 }
382
383 #[inline(always)]
384 #[allow(clippy::result_unit_err)]
385 pub fn decode_msgpack(path: &str, raw: &[u8]) -> Result<Self, ()> {
386 Self::decode_msgpack_impl(path, raw)
387 .map_err(|e| e.set_last())
390 }
391
392 pub fn decode_msgpack_impl(path: &str, raw: &[u8]) -> Result<Self, BoxError> {
393 let mut request_id = None;
394 let mut plugin_name = None;
395 let mut service_name = None;
396 let mut plugin_version = None;
397
398 let mut buffer = Cursor::new(raw);
399 let count = unwrap_ok_or!(rmp::decode::read_map_len(&mut buffer),
400 Err(e) => return Err(BoxError::new(InvalidMsgpack, format!("expected a map: {e}")))
401 );
402 for i in 0..count {
403 let Ok(marker) = rmp::decode::read_marker(&mut buffer) else {
404 #[rustfmt::skip]
405 return Err(BoxError::new(InvalidMsgpack, format!("not enough entries in mapping: expected {count}, got {i}")));
406 };
407 if let Some(field_id) = read_rest_of_uint(marker, &mut buffer)? {
408 match ContextFieldId::try_from(field_id) {
410 Ok(field @ ContextFieldId::RequestId) => {
411 #[rustfmt::skip]
412 let v: Uuid = rmp_serde::decode::from_read(&mut buffer).map_err(|e| decoding_field(&field, invalid_msgpack(e)))?;
413 request_id = Some(v);
414 }
415 Ok(field @ ContextFieldId::PluginName) => {
416 #[rustfmt::skip]
417 let v = msgpack_read_str(&mut buffer).map_err(|e| decoding_field(&field, e))?;
418 plugin_name = Some(v);
419 }
420 Ok(field @ ContextFieldId::ServiceName) => {
421 #[rustfmt::skip]
422 let v = msgpack_read_str(&mut buffer).map_err(|e| decoding_field(&field, e))?;
423 service_name = Some(v);
424 }
425 Ok(field @ ContextFieldId::PluginVersion) => {
426 #[rustfmt::skip]
427 let v = msgpack_read_str(&mut buffer).map_err(|e| decoding_field(&field, e))?;
428 plugin_version = Some(v);
429 }
430 Err(unknown_field_id) => {
431 #[rustfmt::skip]
432 tarantool::say_verbose!("ignoring unknown context field with integer id {unknown_field_id} (0x{unknown_field_id:02x})");
433 skip_value(&mut buffer).map_err(invalid_msgpack)?;
434 }
435 }
436 } else if let Some(_field_name) = msgpack_read_rest_of_str(marker, &mut buffer)? {
437 skip_value(&mut buffer).map_err(invalid_msgpack)?;
440 } else {
441 #[rustfmt::skip]
443 return Err(BoxError::new(InvalidMsgpack, format!("context field must be positive integer or string, got {marker:?}")));
444 }
445 }
446
447 let end_index = buffer.position() as usize;
448 match end_index.cmp(&raw.len()) {
449 #[rustfmt::skip]
450 Ordering::Greater => return Err(BoxError::new(InvalidMsgpack, format!("expected more data after {}", DisplayAsHexBytesLimitted(raw)))),
451 #[rustfmt::skip]
452 Ordering::Less => return Err(BoxError::new(InvalidMsgpack, format!("unexpected data after context: {}", DisplayAsHexBytesLimitted(&raw[end_index..])))),
453 Ordering::Equal => {}
454 }
455
456 let Some(request_id) = request_id else {
457 return Err(invalid_msgpack("context must contain a request_id"));
458 };
459
460 let Some(plugin_name) = plugin_name else {
461 return Err(invalid_msgpack("context must contain a plugin_name"));
462 };
463
464 let Some(service_name) = service_name else {
465 return Err(invalid_msgpack("context must contain a service_name"));
466 };
467
468 let Some(plugin_version) = plugin_version else {
469 return Err(invalid_msgpack("context must contain a plugin_version"));
470 };
471
472 Ok(Self {
473 request_id: request_id.to_tt_uuid(),
474 path: path.into(),
475 plugin_name: plugin_name.into(),
476 service_name: service_name.into(),
477 plugin_version: plugin_version.into(),
478 raw: raw.into(),
479 })
480 }
481}
482
483impl std::fmt::Debug for FfiSafeContext {
484 #[inline]
485 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
486 f.debug_struct("FfiSafeContext")
487 .field(
488 "request_id",
489 &DebugUuid(Uuid::from_tt_uuid(self.request_id)),
490 )
491 .field("path", &self.path)
492 .field("plugin_name", &self.plugin_name)
493 .field("service_name", &self.service_name)
494 .field("plugin_version", &self.plugin_version)
495 .finish_non_exhaustive()
496 }
497}
498
499fn read_rest_of_uint(
504 marker: rmp::Marker,
505 buffer: &mut Cursor<&[u8]>,
506) -> Result<Option<u64>, BoxError> {
507 use rmp::decode::RmpRead as _;
508 match marker {
509 rmp::Marker::FixPos(v) => Ok(Some(v as _)),
510 rmp::Marker::U8 => {
511 let v = buffer.read_data_u8().map_err(invalid_msgpack)?;
512 Ok(Some(v as _))
513 }
514 rmp::Marker::U16 => {
515 let v = buffer.read_data_u16().map_err(invalid_msgpack)?;
516 Ok(Some(v as _))
517 }
518 rmp::Marker::U32 => {
519 let v = buffer.read_data_u32().map_err(invalid_msgpack)?;
520 Ok(Some(v as _))
521 }
522 rmp::Marker::U64 => {
523 let v = buffer.read_data_u64().map_err(invalid_msgpack)?;
524 Ok(Some(v as _))
525 }
526 _ => Ok(None),
527 }
528}
529
530#[track_caller]
531fn log_context_decoding_error(raw: &[u8], e: BoxError) -> BoxError {
532 let location = DisplayErrorLocation(&e);
533 if raw.len() <= 512 {
534 let raw = DisplayAsHexBytesLimitted(raw);
535 tarantool::say_error!("failed to decode context from msgpack ({raw}): {location}{e}");
536 } else {
537 tarantool::say_error!(
538 "failed to decode context from msgpack (too big to display): {location}{e}"
539 );
540 }
541 e
542}
543
544#[inline(always)]
545#[track_caller]
546fn decoding_field(field: &(impl std::fmt::Debug + ?Sized), error: BoxError) -> BoxError {
547 BoxError::new(
548 error.error_code(),
549 format!("failed decoding field {field:?}: {}", error.message()),
550 )
551}
552
553#[inline(always)]
554#[track_caller]
555fn invalid_msgpack(error: impl ToString) -> BoxError {
556 BoxError::new(TarantoolErrorCode::InvalidMsgpack, error.to_string())
557}
558
559struct DebugUuid(Uuid);
561impl std::fmt::Debug for DebugUuid {
562 #[inline(always)]
563 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
564 write!(f, "{}", self.0)
565 }
566}
567
568#[cfg(feature = "internal_test")]
573mod tests {
574 use super::*;
575
576 #[tarantool::test]
577 fn decode_context() {
578 let e = FfiSafeContext::decode_msgpack_impl("path", b"\x90").unwrap_err();
582 #[rustfmt::skip]
583 assert_eq!(e.to_string(), "InvalidMsgpack: expected a map: the type decoded isn't match with the expected one");
584
585 let e = FfiSafeContext::decode_msgpack_impl("path", b"\x80").unwrap_err();
586 #[rustfmt::skip]
587 assert_eq!(e.to_string(), "InvalidMsgpack: context must contain a request_id");
588
589 let e = FfiSafeContext::decode_msgpack_impl("path", b"\x87").unwrap_err();
590 #[rustfmt::skip]
591 assert_eq!(e.to_string(), "InvalidMsgpack: not enough entries in mapping: expected 7, got 0");
592
593 let mut data = Vec::new();
594 data.extend(b"\x84");
595 data.extend(b"\x01\xd8\x020123456789abcdef");
596 data.extend(b"\x02\xa6plugin");
597 data.extend(b"\x03\xa7service");
598 data.extend(b"\x04\xa51.2.3");
599 data.extend(b"unexpected-data");
600 let e = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap_err();
601 #[rustfmt::skip]
602 assert_eq!(e.to_string(), r#"InvalidMsgpack: unexpected data after context: b"unexpected-data""#);
603
604 let mut data = Vec::new();
605 data.extend(b"\x85");
606 data.extend(b"\x01\xd8\x02");
607 let e = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap_err();
608 #[rustfmt::skip]
609 assert_eq!(e.to_string(), "InvalidMsgpack: failed decoding field RequestId: IO error while reading data: unexpected end of file");
610
611 let mut data = Vec::new();
612 data.extend(b"\x85");
613 data.extend(b"\x01\xd8\x020123456789abcdef");
614 data.extend(b"\x02\xa6plugin");
615 data.extend(b"\x03\xa7service");
616 data.extend(b"\x04\xa51.2.3");
617 data.extend(b"\xa3foo\xa4");
618 let e = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap_err();
619 #[rustfmt::skip]
620 assert_eq!(e.to_string(), "InvalidMsgpack: msgpack read error: failed to read MessagePack data");
621
622 let mut data = Vec::new();
623 data.extend(b"\x85");
624 data.extend(b"\x01\xd8\x020123456789abcdef");
625 data.extend(b"\x02\xa6plugin");
626 data.extend(b"\x03\xa7service");
627 data.extend(b"\x04\xa51.2.3");
628 data.extend(b"\xa3foo\x80");
629 let context = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap();
630 let context = Context::new(&context);
631 let e = context.get("foo").unwrap_err();
632 #[rustfmt::skip]
633 assert_eq!(e.to_string(), r#"InvalidMsgpack: failed decoding field "foo": data did not match any variant of untagged enum ContextValue"#);
634
635 let mut data = Vec::new();
639 data.extend(b"\x89");
640 data.extend(b"\x01\xd8\x020123456789abcdef");
641 data.extend(b"\x04\xa51.2.3");
642 data.extend(b"\x03\xa7service");
643 data.extend(b"\x02\xa6plugin");
644 data.extend(b"\xa3foo\xa3bar");
645 data.extend(b"\xa4bool\xc3");
646 data.extend(b"\xa3int\x45");
647 data.extend(b"\xa5float\xcb\x40\x09\x70\xa3\xd7\x0a\x3d\x71");
648 data.extend(b"\xa5array\x93\x01\xa3two\x03");
649
650 let context = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap();
651 let context = Context::new(&context);
652 assert_eq!(
653 context.request_id,
654 Uuid::try_from_slice(b"0123456789abcdef").unwrap()
655 );
656 assert_eq!(context.path, "path");
657 assert_eq!(context.plugin_name, "plugin");
658 assert_eq!(context.service_name, "service");
659 assert_eq!(context.plugin_version, "1.2.3");
660 assert!(context.named_fields.get().is_none());
661
662 assert_eq!(
663 *context.get("foo").unwrap().unwrap(),
664 ContextValue::from("bar")
665 );
666 assert_eq!(
667 *context.get("bool").unwrap().unwrap(),
668 ContextValue::from(true)
669 );
670 assert_eq!(
671 *context.get("int").unwrap().unwrap(),
672 ContextValue::from(69)
673 );
674 assert_eq!(
675 *context.get("float").unwrap().unwrap(),
676 ContextValue::from(3.18)
677 );
678 assert_eq!(
679 *context.get("array").unwrap().unwrap(),
680 ContextValue::Array(vec![
681 ContextValue::from(1),
682 ContextValue::from("two"),
683 ContextValue::from(3)
684 ])
685 );
686 assert!(context.get("bar").unwrap().is_none());
687 }
688
689 #[tarantool::test]
690 fn context_get_set() {
691 let mut data = Vec::new();
692 data.extend(b"\x84");
693 data.extend(b"\x01\xd8\x020123456789abcdef");
694 data.extend(b"\x04\xa51.2.3");
695 data.extend(b"\x03\xa7service");
696 data.extend(b"\x02\xa6plugin");
697 let context = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap();
698 let mut context = Context::new(&context);
699
700 assert!(context.get("bar").unwrap().is_none());
701
702 let old_value = context.set("bar", "string").unwrap();
703 assert!(old_value.is_none());
704 assert_eq!(
705 *context.get("bar").unwrap().unwrap(),
706 ContextValue::from("string")
707 );
708
709 let old_value = context.set("bar", 0xba5).unwrap().unwrap();
710 assert_eq!(old_value, ContextValue::from("string"));
711 assert_eq!(
712 *context.get("bar").unwrap().unwrap(),
713 ContextValue::from(0xba5)
714 );
715
716 assert!(!old_value.is_owned());
717 assert!(old_value.into_owned().is_owned());
718 }
719}