| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![deny(unsafe_op_in_unsafe_fn)] |
| |
| use anyhow::{anyhow, Result}; |
| use quick_xml::events::{attributes::Attribute, BytesStart, Event}; |
| use quick_xml::name::QName; |
| use quick_xml::Reader; |
| use quick_xml::Writer; |
| use std::ffi::{CStr, CString}; |
| use std::io::Cursor; |
| use std::os::raw::c_char; |
| |
| // Copies XML events into vec until reaching a </CamcorderProfiles> event. Will include the |
| // CamcorderProfiles closing tag in the vector. |
| fn consume_camera_profile(rdr: &mut Reader<&[u8]>, vec: &mut Vec<Event>) -> Result<()> { |
| loop { |
| let mut done = false; |
| let ev = rdr.read_event()?; |
| match ev { |
| Event::Eof => { |
| return Err(anyhow!("unexpected end of profile xml")); |
| } |
| Event::End(ref t) => { |
| if QName(b"CamcorderProfiles") == t.name() { |
| done = true; |
| } |
| } |
| _ => {} |
| }; |
| vec.push(ev.into_owned()); |
| if done { |
| return Ok(()); |
| } |
| } |
| } |
| |
| fn safe_filter_camera_config( |
| orig_xml: &CStr, |
| enable_front: bool, |
| enable_back: bool, |
| ) -> Result<CString> { |
| let mut beforep0 = Vec::new(); |
| let mut afterp0 = Vec::new(); |
| let mut current = &mut beforep0; |
| let mut p0 = Vec::new(); |
| let mut p1 = Vec::new(); |
| let mut found_media_settings = false; |
| let xml_str = orig_xml.to_string_lossy(); |
| let mut xml_reader = Reader::from_str(xml_str.as_ref()); |
| |
| loop { |
| match xml_reader.read_event()? { |
| Event::Eof => break, |
| Event::Start(ref t) => { |
| let mut pushorig = true; |
| if QName(b"MediaSettings") == t.name() { |
| found_media_settings = true |
| } else if QName(b"CamcorderProfiles") == t.name() { |
| let pno = match t.try_get_attribute("cameraId")? { |
| None => Err(anyhow!("cameraId attr missing")), |
| Some(ref v) => { |
| let vstr = String::from_utf8_lossy(v.value.as_ref()); |
| if vstr == "0" { |
| Ok(&mut p0) |
| } else if vstr == "1" { |
| Ok(&mut p1) |
| } else { |
| Err(anyhow!("unknown cameraId in media profile content")) |
| } |
| } |
| }?; |
| |
| if pno.len() != 0 { |
| return Err(anyhow!("duplicate cameraId")); |
| } |
| |
| let mut newt = t.to_owned(); |
| newt.clear_attributes(); |
| for maybeattr in t.attributes() { |
| let attr = maybeattr?.to_owned(); |
| if attr.key != QName(b"cameraId") { |
| newt.push_attribute(attr); |
| } |
| } |
| newt.push_attribute(Attribute::from(("cameraId", "0"))); |
| |
| pno.push(Event::Start(newt.into_owned())); |
| consume_camera_profile(&mut xml_reader, pno)?; |
| current = &mut afterp0; |
| pushorig = false; |
| } |
| if pushorig { |
| current.push(Event::Start(t.to_owned())); |
| } |
| } |
| e => current.push(e), |
| } |
| } |
| if !found_media_settings { |
| return Err(anyhow!("could not find MediaSettings tag")); |
| }; |
| if p0.is_empty() != p1.is_empty() { |
| // The original content of media profile may already be filtered by test code[1]. Here we |
| // ensure there's always at least one camera to be tested after applying all filtering. |
| // TODO(b/187239915): Remove filter in test code and unify filter logic here. |
| // [1] |
| // https://source.corp.google.com/chromeos_public/src/third_party/labpack/files/server/cros/camerabox_utils.py;rcl=d30bb56fe7ae9c39b122a28f1d5d2b64f928555c;l=106 |
| return Ok(CString::new(orig_xml.to_bytes())?); |
| } |
| let mut wrt = Writer::new_with_indent(Cursor::new(Vec::new()), ' ' as u8, 4); |
| for ev in beforep0 { |
| wrt.write_event(ev)? |
| } |
| if enable_back { |
| for ev in p0 { |
| wrt.write_event(ev)? |
| } |
| } |
| if enable_front { |
| for ev in p1 { |
| wrt.write_event(ev)? |
| } |
| } |
| for ev in afterp0 { |
| wrt.write_event(ev)? |
| } |
| Ok(CString::new(wrt.into_inner().into_inner())?) |
| } |
| |
| // Transforms the camera config XML in |raw_xml| such that one of the cameras is excluded, and the |
| // other included, with cameraId set to 0 if it is not already. Returns either new XML or an error |
| // message, and sets is_error accordingly. |raw_xml| must be a null-terminated string. |
| #[no_mangle] |
| pub unsafe extern "C" fn filter_camera_config( |
| orig_xml: *const c_char, |
| enable_front: bool, |
| enable_back: bool, |
| is_error: *mut bool, |
| ) -> *mut c_char { |
| unwrap_result( |
| safe_filter_camera_config( |
| // Safe because orig_xml is a null-terminated C string. |
| unsafe { CStr::from_ptr(orig_xml) }, |
| enable_front, |
| enable_back, |
| ), |
| // Safe because is_error is owned by the caller and is non-null. |
| unsafe { &mut *is_error }, |
| ) |
| } |
| |
| fn safe_append_feature_management(orig_xml: &CStr, features: &Vec<&CStr>) -> Result<CString> { |
| let mut wrt = Writer::new_with_indent(Cursor::new(Vec::new()), ' ' as u8, 2); |
| let xml_str = orig_xml.to_string_lossy(); |
| let mut xml_reader = Reader::from_str(xml_str.as_ref()); |
| xml_reader.trim_text(true); |
| loop { |
| match xml_reader.read_event()? { |
| Event::Eof => break, |
| Event::End(ref t) => { |
| if QName(b"permissions") == t.name() { |
| for feat in features { |
| let mut featel = BytesStart::new("feature"); |
| let mut nameat = "org.chromium.arc.feature_management.".to_owned(); |
| nameat.push_str(feat.to_string_lossy().as_ref()); |
| featel.push_attribute(Attribute::from(("name", nameat.as_ref()))); |
| wrt.write_event(Event::Empty(featel))?; |
| } |
| } |
| wrt.write_event(Event::End(t.to_owned()))?; |
| } |
| ev => wrt.write_event(ev.to_owned())?, |
| } |
| } |
| Ok(CString::new(wrt.into_inner().into_inner())?) |
| } |
| |
| fn unwrap_result(r: Result<CString>, is_error: &mut bool) -> *mut c_char { |
| match r { |
| Ok(c) => { |
| *is_error = false; |
| c |
| } |
| Err(e) => { |
| *is_error = true; |
| CString::new(e.to_string()).expect("cannot convert error to string") |
| } |
| } |
| .into_raw() |
| } |
| |
| // Adds one or more <feature> tags, each with a name attr matching what is in the features array. |
| // |raw_xml| must be a null-terminated string. |features| must be an array of null-terminated |
| // strings that ends with a null pointer. Returns either new XML or an error message, and sets |
| // |is_error| accordingly, which must be non-null. |
| #[no_mangle] |
| pub unsafe extern "C" fn append_feature_management( |
| orig_xml: *const c_char, |
| features: *const *const c_char, |
| is_error: *mut bool, |
| ) -> *mut c_char { |
| let mut featvec = Vec::new(); |
| let mut f = features; |
| |
| // Convert C array of strings terminated with a null ptr to a Vec<String>. |
| unsafe { |
| while *f != std::ptr::null() { |
| featvec.push(CStr::from_ptr(*f)); |
| f = f.offset(1); |
| } |
| } |
| |
| unwrap_result( |
| safe_append_feature_management( |
| // Safe because orig_xml is a null-terminated C string. |
| unsafe { CStr::from_ptr(orig_xml) }, |
| &featvec, |
| ), |
| // Safe because is_error is owned by the caller and is non-null. |
| unsafe { &mut *is_error }, |
| ) |
| } |
| |
| // This is only safe if |cstr| is a string returned by append_feature_management or |
| // filter_camera_config. |
| #[no_mangle] |
| pub unsafe extern "C" fn free_rs_string(cstr: *mut c_char) { |
| // Safe because cstr was originally returned by CString::into_raw. |
| unsafe { |
| let _ = CString::from_raw(cstr); |
| } |
| } |