已合并
新增远程授权验证接口 #436
新增远程授权验证接口 #436
已合并
LinShen创建于 7月4日
66 个文件变更+6538-246
MBUILD.gn+14-13
@@ -63,18 +63,19 @@ group("asset_bin_test") {
63 #"test/secure_access_fence/agent_fence:saf_agent_fence_grant_tool_permissions_by_user_local_test"63 #"test/secure_access_fence/agent_fence:saf_agent_fence_grant_tool_permissions_by_user_local_test"
64 ]64 ]
65 if (enable_local_test) {65 if (enable_local_test) {
66- deps += [ "test/asset/unittest/inner_kits_rust:rust_asset_inner_test" ]66+ #deps += [ "test/asset/unittest/inner_kits_rust:rust_asset_inner_test" ]
67- deps += [ "test/asset/unittest/module_test:rust_asset_module_test" ]67+ #deps += [ "test/asset/unittest/module_test:rust_asset_module_test" ]
68- deps += [ "services/asset/common:rust_asset_calling_info_test" ]68+ #deps += [ "services/asset/common:rust_asset_calling_info_test" ]
69- deps += [ "services/asset/db_operator:rust_asset_db_operator_test" ]69+ #deps += [ "services/asset/db_operator:rust_asset_db_operator_test" ]
70- deps += [ "test/asset/unittest/ut_test/services/core_service:rust_asset_core_service_ut_test" ]70+ #deps += [ "test/asset/unittest/ut_test/services/core_service:rust_asset_core_service_ut_test" ]
71- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_test" ]71+ deps += [ "test/secure_access_fence/unittest/ut_test/services/core_service:rust_saf_core_service_ut_test" ]
72- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_manual_test" ]72+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_test" ]
73- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_stress_test" ]73+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_manual_test" ]
74- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_no_permission_test" ]74+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_stress_test" ]
75- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_screen_lock_test" ]75+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_no_permission_test" ]
76- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_screen_lock_hap_identity_test" ]76+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_screen_lock_test" ]
77- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_request_tool_permissions_local_test" ]77+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_screen_lock_hap_identity_test" ]
78- deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_grant_tool_permissions_by_user_local_test" ]78+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_request_tool_permissions_local_test" ]
79+ #deps += [ "test/secure_access_fence/agent_fence:saf_agent_fence_grant_tool_permissions_by_user_local_test" ]
79 }80 }
80}81}
@@ -66,7 +66,8 @@
66 "ylong_runtime",66 "ylong_runtime",
67 "openssl",67 "openssl",
68 "screenlock_mgr",68 "screenlock_mgr",
69- "time_service"69+ "time_service",
70+ "init"
70 ],71 ],
71 "third_party": []72 "third_party": []
72 },73 },
@@ -26,7 +26,8 @@
26 "ohos.permission.MANAGE_TRUSTED_RING",26 "ohos.permission.MANAGE_TRUSTED_RING",
27 "ohos.permission.USE_TRUSTED_RING",27 "ohos.permission.USE_TRUSTED_RING",
28 "ohos.permission.INTERACT_ACROSS_LOCAL_ACCOUNTS",28 "ohos.permission.INTERACT_ACROSS_LOCAL_ACCOUNTS",
29- "ohos.permission.MANAGE_LOCAL_ACCOUNTS"29+ "ohos.permission.MANAGE_LOCAL_ACCOUNTS",
30+ "ohos.permission.sec.ACCESS_UDID"
30 ],31 ],
31 "permission_acls" : [32 "permission_acls" : [
32 "ohos.permission.REMOVE_PERMISSION_USED_RECORD"33 "ohos.permission.REMOVE_PERMISSION_USED_RECORD"
@@ -84,6 +84,8 @@ napi_status NapiGetProperty(const napi_env env, napi_value object, bool &value);
84 84 
85napi_status NapiGetProperty(const napi_env env, napi_value object, int32_t &value);85napi_status NapiGetProperty(const napi_env env, napi_value object, int32_t &value);
86 86 
87+napi_status NapiGetProperty(const napi_env env, napi_value object, int64_t &value);
88+ 
87napi_status NapiGetProperty(const napi_env env, napi_value object, std::string &value);89napi_status NapiGetProperty(const napi_env env, napi_value object, std::string &value);
88 90 
89napi_status NapiGetProperty(const napi_env env, napi_value object, std::vector<SAF::OperationInfo> &operationInfo);91napi_status NapiGetProperty(const napi_env env, napi_value object, std::vector<SAF::OperationInfo> &operationInfo);
@@ -104,6 +106,30 @@ napi_status NapiGetProperty(const napi_env env, napi_value object, SAF::RemoteCo
104 106 
105napi_status NapiGetProperty(const napi_env env, napi_value object, SAF::RemoteInfo &remoteInfo);107napi_status NapiGetProperty(const napi_env env, napi_value object, SAF::RemoteInfo &remoteInfo);
106 108 
109+napi_status NapiGetProperty(const napi_env env, napi_value object,
110+ std::vector<SAF::PermissionQuery> &permissionQueryVector);
111+ 
112+napi_status NapiGetProperty(const napi_env env, napi_value object,
113+ std::vector<SAF::RemoteAuthPackage> &remoteAuthPackageVector);
114+ 
115+napi_status NapiGetProperty(const napi_env env, napi_value object,
116+ SAF::RemoteAuthPackage &remoteAuthPackage);
117+ 
118+napi_status NapiGetProperty(const napi_env env, napi_value object,
119+ SAF::RemoteUserAuthItem &remoteUserAuthItem);
120+ 
121+napi_status NapiGetProperty(const napi_env env, napi_value object,
122+ std::vector<SAF::RemoteUserAuthItem> &remoteUserAuthItemVector);
123+ 
124+napi_status NapiGetProperty(const napi_env env, napi_value object,
125+ SAF::RemoteUserAuthResults &remoteUserAuthResults);
126+ 
127+napi_status NapiGetProperty(const napi_env env, napi_value object,
128+ std::vector<SAF::RemoteUserAuthResults> &remoteUserAuthResultsVector);
129+ 
130+napi_status NapiSetProperty(const napi_env env, napi_value object, const char *propertyName,
131+ const SAF::RemoteAuthPackage &remoteAuthPackage);
132+ 
107napi_status NapiSetProperty(const napi_env env, napi_value object, const char *propertyName, const bool value);133napi_status NapiSetProperty(const napi_env env, napi_value object, const char *propertyName, const bool value);
108 134 
109napi_status NapiSetProperty(const napi_env env, napi_value object, const char *propertyName, const int32_t value);135napi_status NapiSetProperty(const napi_env env, napi_value object, const char *propertyName, const int32_t value);
@@ -12,6 +12,8 @@
12 * See the License for the specific language governing permissions and12 * See the License for the specific language governing permissions and
13 * limitations under the License.13 * limitations under the License.
14 */14 */
15+ 
16+#include <vector>
15#include "napi_common.h"17#include "napi_common.h"
16#include "secure_access_fence_type.h"18#include "secure_access_fence_type.h"
17#include "saf_log.h"19#include "saf_log.h"
@@ -22,8 +24,9 @@ namespace OHOS {
22namespace Security {24namespace Security {
23namespace SAF_ASSET_COMMON {25namespace SAF_ASSET_COMMON {
24namespace {26namespace {
25- #define MAX_BUFF_SIZE 4096 // 4KB27+ constexpr uint32_t MAX_BUFF_SIZE = 1024 * 25; // 25KB
26 constexpr uint32_t MAX_PERMISSION_NAME_SIZE = 256;28 constexpr uint32_t MAX_PERMISSION_NAME_SIZE = 256;
29+ constexpr uint32_t MAX_REMOTE_QUERY_ARRAY_SIZE = 10;
27 AgentFenceErrorCode MapErrorCode(const int32_t safResult)30 AgentFenceErrorCode MapErrorCode(const int32_t safResult)
28 {31 {
29 switch (safResult) {32 switch (safResult) {
@@ -156,14 +159,24 @@ napi_status NapiGetProperty(const napi_env env, napi_value object, int32_t &valu
156 return napi_ok;159 return napi_ok;
157}160}
158 161 
162+napi_status NapiGetProperty(const napi_env env, napi_value object, int64_t &value)
163+{
164+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
165+ NAPI_CALL_RETURN_ERR(env, napi_get_value_int64(env, object, &value));
166+ return napi_ok;
167+}
168+ 
159napi_status NapiGetProperty(const napi_env env, napi_value object, std::string &value)169napi_status NapiGetProperty(const napi_env env, napi_value object, std::string &value)
160{170{
161 NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);171 NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
162 NAPI_THROW_RETURN_ERR(env, type != napi_string, INVALID_PARAMETER, "Invalid type. Expect string");172 NAPI_THROW_RETURN_ERR(env, type != napi_string, INVALID_PARAMETER, "Invalid type. Expect string");
163- char buffer[MAX_BUFF_SIZE] = { 0 };
164 size_t length = 0;173 size_t length = 0;
165- NAPI_CALL_RETURN_ERR(env, napi_get_value_string_utf8(env, object, buffer, MAX_BUFF_SIZE, &length));174+ NAPI_CALL_RETURN_ERR(env, napi_get_value_string_utf8(env, object, nullptr, 0, &length));
166- value = buffer;175+ NAPI_THROW_RETURN_ERR(env, length >= MAX_BUFF_SIZE, INVALID_PARAMETER,
176+ "String length exceeds MAX_BUFF_SIZE");
177+ std::vector<char> buffer(length + 1, 0);
178+ NAPI_CALL_RETURN_ERR(env, napi_get_value_string_utf8(env, object, buffer.data(), length + 1, &length));
179+ value = buffer.data();
167 return napi_ok;180 return napi_ok;
168}181}
169 182 
@@ -439,6 +452,145 @@ napi_status NapiSetProperty(const napi_env env, napi_value object, const char *p
439 return napi_ok;452 return napi_ok;
440}453}
441 454 
455+napi_status NapiGetProperty(const napi_env env, napi_value object,
456+ std::vector<SAF::PermissionQuery> &permissionQueryVector)
457+{
458+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
459+ uint32_t count = 0;
460+ NAPI_CALL_RETURN_ERR(env, napi_get_array_length(env, object, &count));
461+ NAPI_THROW_RETURN_ERR(env, count == 0, GENERAL_PARAMETER_ERROR, "PermissionQuery cannot be empty");
462+ NAPI_THROW_RETURN_ERR(env, count > MAX_REMOTE_QUERY_ARRAY_SIZE, GENERAL_PARAMETER_ERROR,
463+ "PermissionQuery count exceeds limit");
464+ for (uint32_t i = 0; i < count; ++i) {
465+ napi_value item = nullptr;
466+ NAPI_CALL_RETURN_ERR(env, napi_get_element(env, object, i, &item));
467+ SAF::PermissionQuery query;
468+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, item, query));
469+ permissionQueryVector.emplace_back(query);
470+ }
471+ return napi_ok;
472+}
473+ 
474+napi_status NapiGetProperty(const napi_env env, napi_value object,
475+ std::vector<SAF::RemoteAuthPackage> &remoteAuthPackageVector)
476+{
477+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
478+ uint32_t count = 0;
479+ NAPI_CALL_RETURN_ERR(env, napi_get_array_length(env, object, &count));
480+ NAPI_THROW_RETURN_ERR(env, count == 0, GENERAL_PARAMETER_ERROR, "RemoteAuthPackage cannot be empty");
481+ for (uint32_t i = 0; i < count; ++i) {
482+ napi_value item = nullptr;
483+ NAPI_CALL_RETURN_ERR(env, napi_get_element(env, object, i, &item));
484+ SAF::RemoteAuthPackage pkg;
485+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, item, pkg));
486+ remoteAuthPackageVector.emplace_back(pkg);
487+ }
488+ return napi_ok;
489+}
490+ 
491+napi_status NapiGetProperty(const napi_env env, napi_value object,
492+ SAF::RemoteAuthPackage &remoteAuthPackage)
493+{
494+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
495+ napi_value propValue;
496+
497+ napi_status status = napi_get_named_property(env, object, "remoteMessage", &propValue);
498+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "remoteMessage is required");
499+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteAuthPackage.remoteMessage));
500+
501+ status = napi_get_named_property(env, object, "challenge", &propValue);
502+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "challenge is required");
503+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteAuthPackage.challenge));
504+
505+ status = napi_get_named_property(env, object, "ticket", &propValue);
506+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "ticket is required");
507+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteAuthPackage.ticket));
508+
509+ return napi_ok;
510+}
511+ 
512+napi_status NapiGetProperty(const napi_env env, napi_value object,
513+ SAF::RemoteUserAuthItem &remoteUserAuthItem)
514+{
515+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
516+ napi_value propValue;
517+
518+ napi_status status = napi_get_named_property(env, object, "permission", &propValue);
519+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "permission is required");
520+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteUserAuthItem.permission));
521+
522+ status = napi_get_named_property(env, object, "authResult", &propValue);
523+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "authResult is required");
524+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteUserAuthItem.authResult));
525+
526+ return napi_ok;
527+}
528+ 
529+napi_status NapiGetProperty(const napi_env env, napi_value object,
530+ std::vector<SAF::RemoteUserAuthItem> &remoteUserAuthItemVector)
531+{
532+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
533+ uint32_t count = 0;
534+ NAPI_CALL_RETURN_ERR(env, napi_get_array_length(env, object, &count));
535+ NAPI_THROW_RETURN_ERR(env, count == 0, GENERAL_PARAMETER_ERROR, "RemoteUserAuthItem cannot be empty");
536+ for (uint32_t i = 0; i < count; ++i) {
537+ napi_value item = nullptr;
538+ NAPI_CALL_RETURN_ERR(env, napi_get_element(env, object, i, &item));
539+ SAF::RemoteUserAuthItem remoteUserAuthItem;
540+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, item, remoteUserAuthItem));
541+ remoteUserAuthItemVector.emplace_back(remoteUserAuthItem);
542+ }
543+ return napi_ok;
544+}
545+ 
546+napi_status NapiGetProperty(const napi_env env, napi_value object,
547+ SAF::RemoteUserAuthResults &remoteUserAuthResults)
548+{
549+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
550+ napi_value propValue;
551+
552+ napi_status status = napi_get_named_property(env, object, "results", &propValue);
553+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "results is required");
554+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteUserAuthResults.results));
555+ 
556+ status = napi_get_named_property(env, object, "permissionQuery", &propValue);
557+ NAPI_THROW_RETURN_ERR(env, status != napi_ok, INVALID_PARAMETER, "permissionQuery is required");
558+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, propValue, remoteUserAuthResults.permissionQuery));
559+
560+ return napi_ok;
561+}
562+ 
563+napi_status NapiGetProperty(const napi_env env, napi_value object,
564+ std::vector<SAF::RemoteUserAuthResults> &remoteUserAuthResultsVector)
565+{
566+ NAPI_RETURN_IF_VALUE_UNDEFINED(env, object);
567+ uint32_t count = 0;
568+ NAPI_CALL_RETURN_ERR(env, napi_get_array_length(env, object, &count));
569+ NAPI_THROW_RETURN_ERR(env, count == 0, GENERAL_PARAMETER_ERROR, "RemoteUserAuthResults cannot be empty");
570+ NAPI_THROW_RETURN_ERR(env, count > MAX_REMOTE_QUERY_ARRAY_SIZE, GENERAL_PARAMETER_ERROR,
571+ "RemoteUserAuthResults count exceeds limit 10");
572+ for (uint32_t i = 0; i < count; ++i) {
573+ napi_value item = nullptr;
574+ NAPI_CALL_RETURN_ERR(env, napi_get_element(env, object, i, &item));
575+ SAF::RemoteUserAuthResults remoteUserAuthResults;
576+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, item, remoteUserAuthResults));
577+ remoteUserAuthResultsVector.emplace_back(remoteUserAuthResults);
578+ }
579+ return napi_ok;
580+}
581+ 
582+napi_status NapiSetProperty(const napi_env env, napi_value object, const char *propertyName,
583+ const SAF::RemoteAuthPackage &remoteAuthPackage)
584+{
585+ napi_value jsResult = nullptr;
586+ NAPI_CALL_RETURN_ERR(env, napi_create_object(env, &jsResult));
587+ NAPI_CALL_RETURN_ERR(env, NapiSetProperty(env, jsResult, "remoteMessage", remoteAuthPackage.remoteMessage));
588+ NAPI_CALL_RETURN_ERR(env, NapiSetProperty(env, jsResult, "challenge", remoteAuthPackage.challenge));
589+ NAPI_CALL_RETURN_ERR(env, NapiSetProperty(env, jsResult, "ticket", remoteAuthPackage.ticket));
590+ NAPI_CALL_RETURN_ERR(env, napi_set_named_property(env, object, propertyName, jsResult));
591+ return napi_ok;
592+}
593+ 
442napi_value CreateAsyncWork(napi_env env, napi_callback_info info, std::unique_ptr<SAF::AgentFenceAsyncContext> context,594napi_value CreateAsyncWork(napi_env env, napi_callback_info info, std::unique_ptr<SAF::AgentFenceAsyncContext> context,
443 const char *resourceName)595 const char *resourceName)
444{596{
@@ -173,6 +173,12 @@ impl_enum_trait! {
173 /// The error code indicates that invalid plugin.173 /// The error code indicates that invalid plugin.
174 InvalidPlugin = 0x3001B,174 InvalidPlugin = 0x3001B,
175 175 
176+ /// The error code indicates that invalid argument.
177+ InvalidArgument = 0x3001C,
178+ 
179+ /// The error code indicates that invalid argument size.
180+ InvalidArgSize = 0x3001D,
181+ 
176 // ==================== PERMISSION (0x32000) ====================182 // ==================== PERMISSION (0x32000) ====================
177 /// The error code indicates that the caller doesn't have the permission.183 /// The error code indicates that the caller doesn't have the permission.
178 PermissionDenied = 0x32001,184 PermissionDenied = 0x32001,
@@ -190,6 +196,9 @@ impl_enum_trait! {
190 /// The error code indicates that base64 invalid character.196 /// The error code indicates that base64 invalid character.
191 Base64InvalidChar = 0x33004,197 Base64InvalidChar = 0x33004,
192 198 
199+ /// The error code indicates that json parse failed.
200+ JsonParseError = 0x33005,
201+ 
193 // ==================== CLI_TOOL (0x20000) ====================202 // ==================== CLI_TOOL (0x20000) ====================
194 /// The error code indicates that the operation of calling Tool Service is failed.203 /// The error code indicates that the operation of calling Tool Service is failed.
195 ToolError = 0x20001,204 ToolError = 0x20001,
@@ -252,6 +261,14 @@ impl_enum_trait! {
252 261 
253 /// The error code indicates that screen is locked.262 /// The error code indicates that screen is locked.
254 ScreenIsLocked = 0x22002,263 ScreenIsLocked = 0x22002,
264+ 
265+ // ==================== DEVICE_MANAGER (0x23000) ====================
266+ /// The error code indicates that get udid failed
267+ GetUdidFailed = 0x23001,
268+ 
269+ // ==================== FILE_OPERATION (0x24000) ====================
270+ /// The error code indicates that file operation failed.
271+ FileOperationError = 0x24001,
255 }272 }
256}273}
257 274 
@@ -302,3 +319,183 @@ pub trait Verify {
302 /// Verify is correct enum.319 /// Verify is correct enum.
303 fn is_correct_enum(val: u32) -> bool;320 fn is_correct_enum(val: u32) -> bool;
304}321}
322+ 
323+// ============================================================================
324+// Enums
325+// ============================================================================
326+ 
327+/// Operation type enum.
328+#[derive(Debug, Clone, Copy, PartialEq)]
329+pub enum OperationType {
330+ /// CLI operation type.
331+ Cli = 1,
332+ /// API operation type.
333+ Api = 2,
334+}
335+ 
336+/// Role enum.
337+#[derive(Debug, Clone, Copy, PartialEq)]
338+pub enum Role {
339+ /// Controller role.
340+ Controller = 1,
341+ /// Controlled role.
342+ Controlled = 2,
343+}
344+ 
345+/// Grant type enum.
346+#[derive(Debug, Clone, Copy, PartialEq)]
347+pub enum GrantType {
348+ /// Local grant.
349+ LocalGrant = 0x01,
350+ /// Remote grant.
351+ RemoteGrant = 0x02,
352+}
353+// ============================================================================
354+// Basic Structures
355+// ============================================================================
356+ 
357+/// Command info structure.
358+#[derive(Debug, Clone)]
359+pub struct CommandInfo {
360+ /// Command name.
361+ pub cmd_name: String,
362+ /// Sub command.
363+ pub sub_cmd: String,
364+}
365+ 
366+/// Operation info structure.
367+#[derive(Debug, Clone)]
368+pub struct OperationInfo {
369+ /// Operation type.
370+ pub operation_type: OperationType,
371+ /// CLI command info.
372+ pub cli_cmd_info: CommandInfo,
373+ /// Permission string.
374+ pub permission: String,
375+}
376+ 
377+/// Verify ticket info structure (matching IDL definition).
378+#[derive(Debug, Clone)]
379+pub struct VerifyTicketInfo {
380+ /// Message for ticket.
381+ pub message: String,
382+ /// Challenge string.
383+ pub challenge: String,
384+ /// Ticket string.
385+ pub ticket: String,
386+}
387+ 
388+/// CLI info structure (matching IDL definition).
389+#[derive(Debug, Clone)]
390+pub struct CliInfo {
391+ /// Caller token ID.
392+ pub caller_token_id: String,
393+ /// CLI command name.
394+ pub cli_cmd_name: String,
395+ /// Sub CLI command name.
396+ pub sub_cli_cmd_name: String,
397+ /// Permission list for the CLI command.
398+ pub permission_list: Vec<String>,
399+}
400+ 
401+/// Remote control params structure.
402+#[derive(Debug, Clone)]
403+pub struct RemoteControlParams {
404+ /// Challenge string.
405+ pub challenge: String,
406+ /// Remote control ticket.
407+ pub remote_control_ticket: String,
408+ /// Controlled device name.
409+ pub controlled_device_name: String,
410+ /// Controller device name.
411+ pub controller_device_name: String,
412+ /// Sign verify message.
413+ pub sign_verify_msg: String,
414+}
415+ 
416+/// Remote info structure.
417+#[derive(Debug, Clone)]
418+pub struct RemoteInfo {
419+ /// Role.
420+ pub role: Role,
421+ /// Remote ID.
422+ pub remote_id: String,
423+ /// Domain ID.
424+ pub domain_id: String,
425+ /// Remote control params.
426+ pub remote_control_params: RemoteControlParams,
427+}
428+ 
429+/// Permission query structure.
430+#[derive(Debug, Clone)]
431+pub struct PermissionQuery {
432+ /// Operation info list.
433+ pub operation_info: Vec<OperationInfo>,
434+ /// Need ticket flag.
435+ pub need_ticket: bool,
436+ /// Ticket expire time in milliseconds.
437+ pub ticket_expire_time_ms: i32,
438+ /// Caller token ID.
439+ pub caller_token_id: i32,
440+ /// Domain ID.
441+ pub domain_id: String,
442+ /// Remote info.
443+ pub remote_info: RemoteInfo,
444+}
445+ 
446+// ============================================================================
447+// Remote Message Structures
448+// ============================================================================
449+ 
450+/// Device ID header structure.
451+#[derive(Debug, Clone)]
452+pub struct DeviceIdHeader {
453+ /// Controller device ID.
454+ pub controller_device_id: String,
455+ /// Controlled device ID.
456+ pub controlled_device_id: String,
457+}
458+ 
459+/// Remote message structure.
460+#[derive(Debug, Clone)]
461+pub struct RemoteMessage {
462+ /// Device info header.
463+ pub device_info: DeviceIdHeader,
464+ /// Remote auth message.
465+ pub remote_auth_message: String,
466+ /// Caller bundle name resolved from caller_token_id
467+ pub caller_bundle_name: String,
468+}
469+ 
470+/// Remote auth package structure.
471+#[derive(Debug, Clone)]
472+pub struct RemoteAuthPackage {
473+ /// Remote message (JSON string in IDL, struct in Rust for convenience).
474+ pub remote_message: RemoteMessage,
475+ /// Challenge string.
476+ pub challenge: String,
477+ /// Ticket string.
478+ pub ticket: String,
479+}
480+ 
481+// ============================================================================
482+// Controller Device Structures
483+// ============================================================================
484+ 
485+/// Remote user auth item structure.
486+#[derive(Debug, Clone)]
487+pub struct RemoteUserAuthItem {
488+ /// Permission string.
489+ pub permission: String,
490+ /// Authentication result string.
491+ pub auth_result: String,
492+}
493+ 
494+/// Remote user auth results structure.
495+#[derive(Debug, Clone)]
496+pub struct RemoteUserAuthResults {
497+ /// List of authentication result items.
498+ pub results: Vec<RemoteUserAuthItem>,
499+ /// Permission query associated with the authentication.
500+ pub permission_query: PermissionQuery,
501+}
@@ -174,6 +174,83 @@ int32_t SafAgentFence::GrantToolPermissionsByUser(
174 return resultCode;174 return resultCode;
175}175}
176 176 
177+int32_t SafAgentFence::GenerateControlledDevicePackage(
178+ const std::vector<PermissionQuery> &permissionQuery,
179+ std::vector<RemoteAuthPackage> &remoteAuthPackage)
180+{
181+ LOGI("SafAgentFence::GenerateControlledDevicePackage enter");
182+ 
183+ auto proxy = GetProxy(g_mutex);
184+ IF_TRUE_LOGE_RETURN_ERR(proxy == nullptr, SAF_ERR_SERVICE_UNAVAILABLE, "load sa fail.");
185+ 
186+ int32_t resultCode = SAF_SUCCESS;
187+ int32_t ret = HandleIpcError(proxy, [&]() {
188+ return proxy->GenerateControlledDevicePackage(permissionQuery, remoteAuthPackage, resultCode);
189+ });
190+ IF_ERROR_LOGE_RETURN_ERR(ret, SAF_ERR_IPC_PROXY_FAIL, "IPC call failed, ret=%{public}d", ret);
191+ 
192+ LOGI("SafAgentFence::GenerateControlledDevicePackage finished, resultCode = 0x%{public}x", resultCode);
193+ return resultCode;
194+}
195+ 
196+int32_t SafAgentFence::VerifyControlledDevicePackage(
197+ const std::vector<RemoteAuthPackage> &ticketInfo,
198+ std::vector<bool> &verifyRes)
199+{
200+ LOGI("SafAgentFence::VerifyControlledDevicePackage enter");
201+ 
202+ auto proxy = GetProxy(g_mutex);
203+ IF_TRUE_LOGE_RETURN_ERR(proxy == nullptr, SAF_ERR_SERVICE_UNAVAILABLE, "load sa fail.");
204+ 
205+ int32_t resultCode = SAF_SUCCESS;
206+ int32_t ret = HandleIpcError(proxy, [&]() {
207+ return proxy->VerifyControlledDevicePackage(ticketInfo, verifyRes, resultCode);
208+ });
209+ IF_ERROR_LOGE_RETURN_ERR(ret, SAF_ERR_IPC_PROXY_FAIL, "IPC call failed, ret=%{public}d", ret);
210+ 
211+ LOGI("SafAgentFence::VerifyControlledDevicePackage finished, resultCode = 0x%{public}x", resultCode);
212+ return resultCode;
213+}
214+ 
215+int32_t SafAgentFence::GenerateControllerDevicePackage(
216+ const std::vector<RemoteUserAuthResults> &remoteUserAuthResults,
217+ std::vector<RemoteAuthPackage> &remoteAuthPackage)
218+{
219+ LOGI("SafAgentFence::GenerateControllerDevicePackage enter");
220+ 
221+ auto proxy = GetProxy(g_mutex);
222+ IF_TRUE_LOGE_RETURN_ERR(proxy == nullptr, SAF_ERR_SERVICE_UNAVAILABLE, "load sa fail.");
223+ 
224+ int32_t resultCode = SAF_SUCCESS;
225+ int32_t ret = HandleIpcError(proxy, [&]() {
226+ return proxy->GenerateControllerDevicePackage(remoteUserAuthResults, remoteAuthPackage, resultCode);
227+ });
228+ IF_ERROR_LOGE_RETURN_ERR(ret, SAF_ERR_IPC_PROXY_FAIL, "IPC call failed, ret=%{public}d", ret);
229+ 
230+ LOGI("SafAgentFence::GenerateControllerDevicePackage finished, resultCode = 0x%{public}x", resultCode);
231+ return resultCode;
232+}
233+ 
234+int32_t SafAgentFence::VerifyControllerDevicePackage(
235+ const std::vector<RemoteAuthPackage> &ticketInfo,
236+ const RemoteInfo &remoteInfo,
237+ std::vector<bool> &verifyRes)
238+{
239+ LOGI("SafAgentFence::VerifyControllerDevicePackage enter");
240+ 
241+ auto proxy = GetProxy(g_mutex);
242+ IF_TRUE_LOGE_RETURN_ERR(proxy == nullptr, SAF_ERR_SERVICE_UNAVAILABLE, "load sa fail.");
243+ 
244+ int32_t resultCode = SAF_SUCCESS;
245+ int32_t ret = HandleIpcError(proxy, [&]() {
246+ return proxy->VerifyControllerDevicePackage(ticketInfo, remoteInfo, verifyRes, resultCode);
247+ });
248+ IF_ERROR_LOGE_RETURN_ERR(ret, SAF_ERR_IPC_PROXY_FAIL, "IPC call failed, ret=%{public}d", ret);
249+ 
250+ LOGI("SafAgentFence::VerifyControllerDevicePackage finished, resultCode = 0x%{public}x", resultCode);
251+ return resultCode;
252+}
253+ 
177int32_t SafAgentFence::VerifyTicket(254int32_t SafAgentFence::VerifyTicket(
178 int32_t osAccountId,255 int32_t osAccountId,
179 const std::string &callerId,256 const std::string &callerId,
@@ -92,6 +92,7 @@ ohos_rust_static_library("saf_ipc") {
92 deps = [92 deps = [
93 "../../definition:saf_definition",93 "../../definition:saf_definition",
94 "../../os_dependency/log:saf_log",94 "../../os_dependency/log:saf_log",
95+ "../../utils:saf_utils",
95 ]96 ]
96 external_deps = [ "ipc:ipc_rust" ]97 external_deps = [ "ipc:ipc_rust" ]
97 98 
@@ -21,4 +21,5 @@ edition = "2021"
21[dependencies]21[dependencies]
22saf_definition = { path = "../../definition" }22saf_definition = { path = "../../definition" }
23saf_log = { path = "../../os_dependency/log" }23saf_log = { path = "../../os_dependency/log" }
24+saf_utils = { path = "../../utils" }
24ipc = { git = "https://gitcode.com/openharmony/communication_ipc.git" }25ipc = { git = "https://gitcode.com/openharmony/communication_ipc.git" }
@@ -56,6 +56,55 @@ interface OHOS.Security.SAF.ISecureAccessFence {
56 [out] int resultCode56 [out] int resultCode
57 );57 );
58 58 
59+ /**
60+ * @brief Generate controlled device packages for remote control scenario.
61+ * @param permissionQuery List of permission queries.
62+ * @param remoteAuthPackage List of generated remote auth packages (output parameter).
63+ */
64+ [ipccode 4] void GenerateControlledDevicePackage(
65+ [in] List<PermissionQuery> permissionQuery,
66+ [out] List<RemoteAuthPackage> remoteAuthPackage,
67+ [out] int resultCode
68+ );
69+ 
70+ /**
71+ * @brief Verify controlled device packages for remote control scenario.
72+ * @param ticketInfo List of remote auth packages to verify.
73+ * @param verifyRes List of verification results (output parameter).
74+ * @param resultCode Error code output (0 for success).
75+ */
76+ [ipccode 5] void VerifyControlledDevicePackage(
77+ [in] List<RemoteAuthPackage> ticketInfo,
78+ [out] List<boolean> verifyRes,
79+ [out] int resultCode
80+ );
81+
82+ /**
83+ * @brief Generate controller device packages for remote control scenario.
84+ * @param remoteUserAuthResults List of remote user authentication results.
85+ * @param remoteAuthPackage List of generated remote auth packages (output parameter).
86+ * @param resultCode Error code output (0 for success).
87+ */
88+ [ipccode 6] void GenerateControllerDevicePackage(
89+ [in] List<RemoteUserAuthResults> remoteUserAuthResults,
90+ [out] List<RemoteAuthPackage> remoteAuthPackage,
91+ [out] int resultCode
92+ );
93+ 
94+ /**
95+ * @brief Verify controller device packages for remote control scenario.
96+ * @param ticketInfo List of remote auth packages to verify.
97+ * @param remoteInfo Remote information for verification.
98+ * @param verifyRes List of verification results (output parameter).
99+ * @param resultCode Error code output (0 for success).
100+ */
101+ [ipccode 7] void VerifyControllerDevicePackage(
102+ [in] List<RemoteAuthPackage> ticketInfo,
103+ [in] RemoteInfo remoteInfo,
104+ [out] List<boolean> verifyRes,
105+ [out] int resultCode
106+ );
107+ 
59 /**108 /**
60 * @brief Batch query command permissions.109 * @brief Batch query command permissions.
61 * @param cmds List of commands to query (input parameter).110 * @param cmds List of commands to query (input parameter).
@@ -149,4 +149,30 @@ struct PermissionQuery {
149struct UserAuthResult {149struct UserAuthResult {
150 List<PermissionInfo> permissionInfo;150 List<PermissionInfo> permissionInfo;
151 PermissionQuery permissionQuery;151 PermissionQuery permissionQuery;
152-};152+};
153+ 
154+struct RemoteAuthPackage {
155+ String remoteMessage;
156+ String challenge;
157+ String ticket;
158+};
159+ 
160+/**
161+ * @brief Remote user authentication item for controller device.
162+ */
163+struct RemoteUserAuthItem {
164+ /** Permission string. */
165+ String permission;
166+ /** Authentication result string. */
167+ String authResult;
168+};
169+ 
170+/**
171+ * @brief Remote user authentication results for controller device.
172+ */
173+struct RemoteUserAuthResults {
174+ /** List of authentication result items. */
175+ List<RemoteUserAuthItem> results;
176+ /** Permission query associated with the authentication. */
177+ PermissionQuery permissionQuery;
178+};
@@ -15,12 +15,33 @@
15 15 
16//! This module defines IPC interfaces and constants.16//! This module defines IPC interfaces and constants.
17 17 
18+pub mod remote_message_wrapper;
19+pub mod sub_modules;
20+ 
18use ipc::{21use ipc::{
19 parcel::{Deserialize, MsgParcel},22 parcel::{Deserialize, MsgParcel},
20 IpcStatusCode,23 IpcStatusCode,
21};24};
22 25 
23-use saf_definition::{macros_lib, Conversion, DataType, ErrCode, Result, SAFError, SAFMap, Tag, Value};26+use saf_definition::{
27+ macros_lib, Conversion, DataType, ErrCode, Result, SAFError, SAFMap, Tag, Value, VerifyTicketInfo
28+};
29+ 
30+pub use sub_modules::{
31+ deserialize_permission_queries,
32+ deserialize_remote_auth_packages,
33+ deserialize_remote_info,
34+ deserialize_remote_user_auth_results_vec,
35+ deserialize_verify_ticket_infos,
36+ serialize_bool_vec,
37+ serialize_cli_infos,
38+ serialize_remote_auth_packages,
39+ serialize_verify_ticket_infos,
40+};
41+ 
42+// ============================================================================
43+// Constants
44+// ============================================================================
24 45 
25/// SA id for SAF service.46/// SA id for SAF service.
26pub const SA_ID: i32 = 66532;47pub const SA_ID: i32 = 66532;
@@ -28,19 +49,24 @@ pub const SA_ID: i32 = 66532;
28pub const SA_NAME: &str = "OHOS.Security.SAF.ISecureAccessFence";49pub const SA_NAME: &str = "OHOS.Security.SAF.ISecureAccessFence";
29/// IPC result code.50/// IPC result code.
30pub const IPC_SUCCESS: u32 = 0;51pub const IPC_SUCCESS: u32 = 0;
31- 
32/// IPC code for BatchGenerateTicket.52/// IPC code for BatchGenerateTicket.
33-pub const CMD_BATCH_GENERATE_TICKET: u32 = 1;53+pub const CMD_BATCH_GENERATE_TICKET: u32 = 1;
34-/// IPC code for BatchVerifyTicket.54+/// IPC code for BatchVerifyTicket.
35-pub const CMD_BATCH_VERIFY_TICKET: u32 = 2;55+pub const CMD_BATCH_VERIFY_TICKET: u32 = 2;
36-/// IPC code for VerifyTicket.56+/// IPC code for VerifyTicket.
37-pub const CMD_VERIFY_TICKET: u32 = 3;57+pub const CMD_VERIFY_TICKET: u32 = 3;
38-/// IPC code for BatchQueryCommandPermission.58+/// IPC code for GenerateControlledDevicePackage.
39-pub const CMD_BATCH_QUERY_COMMAND_PERMISSION: u32 = 500;59+pub const CMD_GENERATE_CONTROLLED_DEVICE_PACKAGE: u32 = 4;
60+/// IPC code for VerifyControlledDevicePackage.
61+pub const CMD_VERIFY_CONTROLLED_DEVICE_PACKAGE: u32 = 5;
62+/// IPC code for GenerateControllerDevicePackage.
63+pub const CMD_GENERATE_CONTROLLER_DEVICE_PACKAGE: u32 = 6;
64+/// IPC code for VerifyControllerDevicePackage.
65+pub const CMD_VERIFY_CONTROLLER_DEVICE_PACKAGE: u32 = 7;
40 66 
41const MAX_MAP_CAPACITY: u32 = 64;67const MAX_MAP_CAPACITY: u32 = 64;
42-const MAX_VEC_CAPACITY: u32 = 0x10000;68+pub(crate) const MAX_VEC_CAPACITY: u32 = 0x10000;
43-const MAX_TICKET_CAPACITY: u32 = 99;69+pub(crate) const MAX_TICKET_CAPACITY: u32 = 99;
44 70 
45macros_lib::impl_enum_trait! {71macros_lib::impl_enum_trait! {
46 /// Code used to identify the function to be called.72 /// Code used to identify the function to be called.
@@ -52,28 +78,18 @@ macros_lib::impl_enum_trait! {
52 }78 }
53}79}
54 80 
55-/// Verify ticket info structure (matching IDL definition).81+// ============================================================================
56-#[derive(Debug, Clone)]82+// Error Handling
57-pub struct VerifyTicketInfo {83+// ============================================================================
58- /// Message for ticket.
59- pub message: String,
60- /// Challenge string.
61- pub challenge: String,
62- /// Ticket string.
63- pub ticket: String,
64-}
65 84 
66-/// CLI info structure (matching IDL definition).85+/// Convert ipc error into SAF error.
67-#[derive(Debug, Clone)]86+pub fn ipc_err_handle(e: IpcStatusCode) -> SAFError {
68-pub struct CliInfo {87+ match e {
69- /// Caller token ID.88+ IpcStatusCode::ServiceDied => {
70- pub caller_token_id: String,89+ SAFError::new(ErrCode::ServiceUnavailable, format!("[FATAL][IPC]Ipc status code = {}", e as i32))
71- /// CLI command name.90+ },
72- pub cli_cmd_name: String,91+ _ => SAFError::new(ErrCode::IpcError, format!("[FATAL][IPC]Ipc status code = {}", e)),
73- /// Sub CLI command name.92+ }
74- pub sub_cli_cmd_name: String,
75- /// Permission list for the CLI command.
76- pub permission_list: Vec<String>,
77}93}
78 94 
79/// deserialize T from parcel95/// deserialize T from parcel
@@ -87,9 +103,13 @@ pub fn deserialize<T: Deserialize>(parcel: &mut MsgParcel) -> Result<T> {
87 Ok(value)103 Ok(value)
88}104}
89 105 
106+// ============================================================================
107+// Map Serialization
108+// ============================================================================
109+ 
90/// serialize the map to parcel110/// serialize the map to parcel
91pub fn serialize_map(map: &SAFMap, parcel: &mut MsgParcel) -> Result<()> {111pub fn serialize_map(map: &SAFMap, parcel: &mut MsgParcel) -> Result<()> {
92- if map.len() as u32 > MAX_MAP_CAPACITY {112+ if map.len() > MAX_MAP_CAPACITY as usize {
93 return macros_lib::log_throw_error!(113 return macros_lib::log_throw_error!(
94 ErrCode::IpcWriteDataFail,114 ErrCode::IpcWriteDataFail,
95 "[FATAL][IPC]The map size exceeds the limit."115 "[FATAL][IPC]The map size exceeds the limit."
@@ -120,15 +140,15 @@ pub fn serialize_map(map: &SAFMap, parcel: &mut MsgParcel) -> Result<()> {
120 140 
121/// deserialize the map from parcel141/// deserialize the map from parcel
122pub fn deserialize_map(parcel: &mut MsgParcel) -> Result<SAFMap> {142pub fn deserialize_map(parcel: &mut MsgParcel) -> Result<SAFMap> {
123- let len = parcel.read::<u32>().map_err(ipc_err_handle)?;143+ let count = parcel.read::<u32>().map_err(ipc_err_handle)?;
124- if len > MAX_MAP_CAPACITY {144+ if count > MAX_MAP_CAPACITY {
125 return macros_lib::log_throw_error!(145 return macros_lib::log_throw_error!(
126 ErrCode::IpcReadDataFail,146 ErrCode::IpcReadDataFail,
127 "[FATAL][IPC]The map size exceeds the limit."147 "[FATAL][IPC]The map size exceeds the limit."
128 );148 );
129 }149 }
130- let mut map = SAFMap::with_capacity(len as usize);150+ let mut map = SAFMap::with_capacity(count as usize);
131- for _ in 0..len {151+ for _ in 0..count {
132 let tag = parcel.read::<u32>().map_err(ipc_err_handle)?;152 let tag = parcel.read::<u32>().map_err(ipc_err_handle)?;
133 let tag = Tag::try_from(tag)?;153 let tag = Tag::try_from(tag)?;
134 match tag.data_type() {154 match tag.data_type() {
@@ -178,29 +198,23 @@ pub fn serialize_maps(vec: &Vec<SAFMap>, parcel: &mut MsgParcel) -> Result<()> {
178 198 
179/// Deserialize the collection of map from parcel.199/// Deserialize the collection of map from parcel.
180pub fn deserialize_maps(parcel: &mut MsgParcel) -> Result<Vec<SAFMap>> {200pub fn deserialize_maps(parcel: &mut MsgParcel) -> Result<Vec<SAFMap>> {
181- let len = parcel.read::<u32>().map_err(ipc_err_handle)?;201+ let count = parcel.read::<u32>().map_err(ipc_err_handle)?;
182- if len > MAX_VEC_CAPACITY {202+ if count > MAX_VEC_CAPACITY {
183 return macros_lib::log_throw_error!(203 return macros_lib::log_throw_error!(
184 ErrCode::IpcReadDataFail,204 ErrCode::IpcReadDataFail,
185 "[FATAL][IPC]The vector size exceeds the limit."205 "[FATAL][IPC]The vector size exceeds the limit."
186 );206 );
187 }207 }
188- let mut res_vec = Vec::with_capacity(len as usize);208+ let mut res_vec = Vec::with_capacity(count as usize);
189- for _i in 0..len {209+ for _i in 0..count {
190 res_vec.push(deserialize_map(parcel)?);210 res_vec.push(deserialize_map(parcel)?);
191 }211 }
192 Ok(res_vec)212 Ok(res_vec)
193}213}
194 214 
195-/// Convert ipc error into SAF error.215+// ============================================================================
196-pub fn ipc_err_handle(e: IpcStatusCode) -> SAFError {216+// Batch Request Deserialization (for stub)
197- match e {217+// ============================================================================
198- IpcStatusCode::ServiceDied => {
199- SAFError::new(ErrCode::ServiceUnavailable, format!("[FATAL][IPC]Ipc status code = {}", e as i32))
200- },
201- _ => SAFError::new(ErrCode::IpcError, format!("[FATAL][IPC]Ipc status code = {}", e)),
202- }
203-}
204 218 
205/// Deserialize BatchGenerateTicket request parameters from MsgParcel.219/// Deserialize BatchGenerateTicket request parameters from MsgParcel.
206pub fn deserialize_batch_generate_ticket_request(parcel: &mut MsgParcel) -> Result<(i32, String, Vec<String>)> {220pub fn deserialize_batch_generate_ticket_request(parcel: &mut MsgParcel) -> Result<(i32, String, Vec<String>)> {
@@ -218,53 +232,14 @@ pub fn deserialize_batch_verify_ticket_request(parcel: &mut MsgParcel) -> Result
218 Ok((os_account_id, caller_id, verify_infos))232 Ok((os_account_id, caller_id, verify_infos))
219}233}
220 234 
221-/// Deserialize VerifyTicketInfo from MsgParcel.235+/// Deserialize VerifyTicket request parameters from MsgParcel.
222-pub fn deserialize_verify_ticket_info(parcel: &mut MsgParcel) -> Result<VerifyTicketInfo> {236+pub fn deserialize_verify_ticket_request(
223- let message = parcel.read_string16().map_err(ipc_err_handle)?;237+ parcel: &mut MsgParcel,
224- let challenge = parcel.read_string16().map_err(ipc_err_handle)?;238+) -> Result<(i32, String, String)> {
225- let ticket = parcel.read_string16().map_err(ipc_err_handle)?;239+ let os_account_id = parcel.read::<i32>().map_err(ipc_err_handle)?;
226- Ok(VerifyTicketInfo { message, challenge, ticket })240+ let caller_id = parcel.read_string16().map_err(ipc_err_handle)?;
227-}241+ let verify_info = parcel.read_string16().map_err(ipc_err_handle)?;
228- 242+ Ok((os_account_id, caller_id, verify_info))
229-/// Deserialize vector of VerifyTicketInfo from MsgParcel.
230-pub fn deserialize_verify_ticket_infos(parcel: &mut MsgParcel) -> Result<Vec<VerifyTicketInfo>> {
231- let len = parcel.read::<i32>().map_err(ipc_err_handle)?;
232- if len < 0 || len as u32 > MAX_TICKET_CAPACITY {
233- return macros_lib::log_throw_error!(
234- ErrCode::InvalidArrayLen,
235- "[FATAL][IPC]VerifyTicketInfo vector size invalid: {}",
236- len
237- );
238- }
239- let mut vec = Vec::with_capacity(len as usize);
240- for _ in 0..len {
241- vec.push(deserialize_verify_ticket_info(parcel)?);
242- }
243- Ok(vec)
244-}
245- 
246-/// Serialize VerifyTicketInfo to MsgParcel (for reply).
247-pub fn serialize_verify_ticket_info(info: &VerifyTicketInfo, parcel: &mut MsgParcel) -> Result<()> {
248- parcel.write_string16(&info.message).map_err(ipc_err_handle)?;
249- parcel.write_string16(&info.challenge).map_err(ipc_err_handle)?;
250- parcel.write_string16(&info.ticket).map_err(ipc_err_handle)?;
251- Ok(())
252-}
253- 
254-/// Serialize vector of VerifyTicketInfo to MsgParcel (for reply).
255-pub fn serialize_verify_ticket_infos(infos: &Vec<VerifyTicketInfo>, parcel: &mut MsgParcel) -> Result<()> {
256- if infos.len() as u32 > MAX_TICKET_CAPACITY {
257- return macros_lib::log_throw_error!(
258- ErrCode::InvalidArrayLen,
259- "[FATAL][IPC]VerifyTicketInfo vector size exceeds limit: {}",
260- infos.len()
261- );
262- }
263- parcel.write::<i32>(&(infos.len() as i32)).map_err(ipc_err_handle)?;
264- for info in infos {
265- serialize_verify_ticket_info(info, parcel)?;
266- }
267- Ok(())
268}243}
269 244 
270/// Serialize vector of i32 to MsgParcel (for reply).245/// Serialize vector of i32 to MsgParcel (for reply).
@@ -276,63 +251,13 @@ pub fn serialize_i32_vec(vec: &Vec<i32>, parcel: &mut MsgParcel) -> Result<()> {
276 vec.len()251 vec.len()
277 );252 );
278 }253 }
279- parcel.write::<i32>(&(vec.len() as i32)).map_err(ipc_err_handle)?;254+ let count = i32::try_from(vec.len()).map_err(|_| {
255+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidArrayLen,
256+ "[FATAL][IPC]i32 vector length overflows i32")
257+ })?;
258+ parcel.write::<i32>(&count).map_err(ipc_err_handle)?;
280 for val in vec {259 for val in vec {
281 parcel.write::<i32>(val).map_err(ipc_err_handle)?;260 parcel.write::<i32>(val).map_err(ipc_err_handle)?;
282 }261 }
283 Ok(())262 Ok(())
284-}263+}
285- 
286-/// Deserialize VerifyTicket request parameters from MsgParcel.
287-pub fn deserialize_verify_ticket_request(
288- parcel: &mut MsgParcel,
289-) -> Result<(i32, String, String)> {
290- let os_account_id = parcel.read::<i32>().map_err(ipc_err_handle)?;
291- let caller_id = parcel.read_string16().map_err(ipc_err_handle)?;
292- let verify_info = parcel.read_string16().map_err(ipc_err_handle)?;
293- Ok((os_account_id, caller_id, verify_info))
294-}
295- 
296-/// Serialize CliInfo to MsgParcel (for reply).
297-pub fn serialize_cli_info(info: &CliInfo, parcel: &mut MsgParcel) -> Result<()> {
298- parcel.write_string16(&info.caller_token_id).map_err(ipc_err_handle)?;
299- parcel.write_string16(&info.cli_cmd_name).map_err(ipc_err_handle)?;
300- parcel.write_string16(&info.sub_cli_cmd_name).map_err(ipc_err_handle)?;
301- serialize_string_vec(&info.permission_list, parcel)?;
302- Ok(())
303-}
304- 
305-/// Write vector length to parcel with capacity check.
306-pub fn write_vec_len(vec_len: usize, max_capacity: i32, err_code: ErrCode, label: &str,
307- parcel: &mut MsgParcel) -> Result<()> {
308- if vec_len as i32 > max_capacity {
309- return macros_lib::log_throw_error!(
310- err_code,
311- "[FATAL][IPC]{} vector size exceeds limit: {}",
312- label,
313- vec_len
314- );
315- }
316- parcel.write::<i32>(&(vec_len as i32)).map_err(ipc_err_handle)?;
317- Ok(())
318-}
319- 
320-/// Serialize vector of CliInfo to MsgParcel (for reply).
321-pub fn serialize_cli_infos(infos: &Vec<CliInfo>, parcel: &mut MsgParcel) -> Result<()> {
322- write_vec_len(infos.len(), MAX_TICKET_CAPACITY as i32, ErrCode::InvalidArrayLen,
323- "CliInfo", parcel)?;
324- for info in infos {
325- serialize_cli_info(info, parcel)?;
326- }
327- Ok(())
328-}
329- 
330-/// Serialize string vector to MsgParcel (for reply).
331-pub fn serialize_string_vec(vec: &Vec<String>, parcel: &mut MsgParcel) -> Result<()> {
332- write_vec_len(vec.len(), MAX_TICKET_CAPACITY as i32, ErrCode::InvalidArrayLen,
333- "string", parcel)?;
334- for s in vec {
335- parcel.write_string16(s).map_err(ipc_err_handle)?;
336- }
337- Ok(())
338-}
@@ -0,0 +1,98 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! RemoteMessage JSON serialization and deserialization utilities.
17+ 
18+use saf_utils::{JsonBuilder, JsonValue, Object, new_object, object_add_string, take_optional_string,
19+ take_required_string, take_required_value, json_into_object};
20+use saf_definition::{macros_lib, ErrCode, Result, DeviceIdHeader, RemoteMessage};
21+ 
22+/// Remote message JSON field keys for serialization/deserialization.
23+pub struct RemoteMessageKeys {
24+ /// The device info object key
25+ pub device_info: &'static str,
26+ /// The controller device ID key (inside deviceInfo)
27+ pub controller_device_id: &'static str,
28+ /// The controlled device ID key (inside deviceInfo)
29+ pub controlled_device_id: &'static str,
30+ /// The remote auth message key
31+ pub remote_auth_message: &'static str,
32+ /// The caller bundle name key
33+ pub caller_bundle_name: &'static str,
34+}
35+ 
36+/// Global constant instance for remote message JSON field keys.
37+pub const REMOTE_MESSAGE_KEYS: RemoteMessageKeys = RemoteMessageKeys {
38+ device_info: "deviceInfo",
39+ controller_device_id: "controllerDeviceId",
40+ controlled_device_id: "controlledDeviceId",
41+ remote_auth_message: "remoteAuthMessage",
42+ caller_bundle_name: "callerBundleName",
43+};
44+ 
45+/// Serialize DeviceIdHeader to JSON string
46+pub fn serialize_device_id_header_to_json(header: &DeviceIdHeader) -> Result<String> {
47+ let mut builder = JsonBuilder::new();
48+ builder.add_string(REMOTE_MESSAGE_KEYS.controller_device_id, &header.controller_device_id);
49+ builder.add_string(REMOTE_MESSAGE_KEYS.controlled_device_id, &header.controlled_device_id);
50+ builder.build()
51+}
52+ 
53+/// Deserialize DeviceIdHeader from an Object (the "deviceInfo" sub-object), consuming the fields
54+pub fn parse_device_id_header_from_json(device_info_obj: &mut Object) -> Result<DeviceIdHeader> {
55+ let controller_device_id = take_optional_string(device_info_obj, REMOTE_MESSAGE_KEYS.controller_device_id)?;
56+ let controlled_device_id = take_optional_string(device_info_obj, REMOTE_MESSAGE_KEYS.controlled_device_id)?;
57+ 
58+ Ok(DeviceIdHeader {
59+ controller_device_id,
60+ controlled_device_id
61+ })
62+}
63+ 
64+/// Serialize RemoteMessage to JSON string.
65+pub fn serialize_remote_message_to_json(message: &RemoteMessage) -> Result<String> {
66+ let mut device_info_obj = new_object();
67+ object_add_string(&mut device_info_obj, REMOTE_MESSAGE_KEYS.controller_device_id,
68+ &message.device_info.controller_device_id);
69+ object_add_string(&mut device_info_obj, REMOTE_MESSAGE_KEYS.controlled_device_id,
70+ &message.device_info.controlled_device_id);
71+
72+ let mut builder = JsonBuilder::new();
73+ builder.add_object(REMOTE_MESSAGE_KEYS.device_info, device_info_obj);
74+ builder.add_string(REMOTE_MESSAGE_KEYS.remote_auth_message, &message.remote_auth_message);
75+ builder.add_string(REMOTE_MESSAGE_KEYS.caller_bundle_name, &message.caller_bundle_name);
76+ builder.build()
77+}
78+ 
79+/// Deserialize RemoteMessage from JSON string.
80+pub fn deserialize_remote_message_from_json(json_str: &str) -> Result<RemoteMessage> {
81+ let json = JsonValue::from_text(json_str).map_err(|e| {
82+ macros_lib::log_and_into_saf_error!(
83+ ErrCode::JsonParseError,
84+ "[FATAL][IPC]Failed to parse remoteMessage JSON: {}", e
85+ )
86+ })?;
87+ let mut obj = json_into_object(json)?;
88+ let mut device_info_obj = json_into_object(take_required_value(&mut obj, REMOTE_MESSAGE_KEYS.device_info)?)?;
89+ let device_info = parse_device_id_header_from_json(&mut device_info_obj)?;
90+ let remote_auth_message = take_required_string(&mut obj, REMOTE_MESSAGE_KEYS.remote_auth_message)?;
91+ let caller_bundle_name = take_optional_string(&mut obj, REMOTE_MESSAGE_KEYS.caller_bundle_name)?;
92+
93+ Ok(RemoteMessage {
94+ device_info,
95+ remote_auth_message,
96+ caller_bundle_name
97+ })
98+}
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Sub-modules for IPC serialization.
17+ 
18+pub mod ticket;
19+pub mod remote_control;
20+ 
21+pub use ticket::{
22+ deserialize_verify_ticket_infos,
23+ serialize_verify_ticket_infos,
24+ serialize_cli_infos,
25+};
26+ 
27+pub use remote_control::{
28+ deserialize_permission_queries,
29+ deserialize_remote_info,
30+ deserialize_remote_auth_packages,
31+ deserialize_remote_user_auth_results_vec,
32+ serialize_bool_vec,
33+ serialize_remote_auth_packages,
34+};
@@ -0,0 +1,342 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Remote control related serialization functions.
17+ 
18+use ipc::parcel::MsgParcel;
19+use saf_definition::{
20+ macros_lib, ErrCode, Result,
21+ CommandInfo, OperationInfo, OperationType,
22+ RemoteControlParams, RemoteInfo, Role,
23+ PermissionQuery, RemoteAuthPackage,
24+ RemoteUserAuthItem, RemoteUserAuthResults,
25+};
26+use crate::{ipc_err_handle, MAX_TICKET_CAPACITY, MAX_VEC_CAPACITY};
27+use crate::remote_message_wrapper;
28+ 
29+/// Deserialize CommandInfo from MsgParcel.
30+pub fn deserialize_command_info(parcel: &mut MsgParcel) -> Result<CommandInfo> {
31+ let cmd_name = parcel.read_string16().map_err(ipc_err_handle)?;
32+ let sub_cmd = parcel.read_string16().map_err(ipc_err_handle)?;
33+ Ok(CommandInfo { cmd_name, sub_cmd })
34+}
35+ 
36+/// Serialize CommandInfo to MsgParcel.
37+pub fn serialize_command_info(info: &CommandInfo, parcel: &mut MsgParcel) -> Result<()> {
38+ parcel.write_string16(&info.cmd_name).map_err(ipc_err_handle)?;
39+ parcel.write_string16(&info.sub_cmd).map_err(ipc_err_handle)?;
40+ Ok(())
41+}
42+ 
43+/// Deserialize OperationInfo from MsgParcel.
44+pub fn deserialize_operation_info(parcel: &mut MsgParcel) -> Result<OperationInfo> {
45+ let operation_type_val = parcel.read::<u32>().map_err(ipc_err_handle)?;
46+ let operation_type = match operation_type_val {
47+ 1 => OperationType::Cli,
48+ 2 => OperationType::Api,
49+ _ => return macros_lib::log_throw_error!(
50+ ErrCode::InvalidArgument,
51+ "[FATAL][IPC]Invalid operation type: {}",
52+ operation_type_val
53+ ),
54+ };
55+ let cli_cmd_info = deserialize_command_info(parcel)?;
56+ let permission = parcel.read_string16().map_err(ipc_err_handle)?;
57+ Ok(OperationInfo {
58+ operation_type,
59+ cli_cmd_info,
60+ permission,
61+ })
62+}
63+ 
64+/// Deserialize OperationInfo vector from MsgParcel.
65+pub fn deserialize_operation_infos(parcel: &mut MsgParcel) -> Result<Vec<OperationInfo>> {
66+ let count = parcel.read::<i32>().map_err(ipc_err_handle)?;
67+ if count < 0 || count as u32 > MAX_TICKET_CAPACITY {
68+ return macros_lib::log_throw_error!(
69+ ErrCode::InvalidArrayLen,
70+ "[FATAL][IPC]OperationInfo vector size invalid: {}",
71+ count
72+ );
73+ }
74+ let mut vec = Vec::with_capacity(count as usize);
75+ for _ in 0..count {
76+ vec.push(deserialize_operation_info(parcel)?);
77+ }
78+ Ok(vec)
79+}
80+ 
81+/// Serialize OperationInfo to MsgParcel.
82+pub fn serialize_operation_info(info: &OperationInfo, parcel: &mut MsgParcel) -> Result<()> {
83+ parcel.write::<u32>(&(info.operation_type as u32)).map_err(ipc_err_handle)?;
84+ serialize_command_info(&info.cli_cmd_info, parcel)?;
85+ parcel.write_string16(&info.permission).map_err(ipc_err_handle)?;
86+ Ok(())
87+}
88+ 
89+/// Serialize OperationInfo vector to MsgParcel.
90+pub fn serialize_operation_infos(infos: &Vec<OperationInfo>, parcel: &mut MsgParcel) -> Result<()> {
91+ if infos.len() as u32 > MAX_TICKET_CAPACITY {
92+ return macros_lib::log_throw_error!(
93+ ErrCode::InvalidArrayLen,
94+ "[FATAL][IPC]OperationInfo vector size exceeds limit: {}",
95+ infos.len()
96+ );
97+ }
98+ let count = i32::try_from(infos.len()).map_err(|_| {
99+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidArrayLen,
100+ "[FATAL][IPC]OperationInfo vector length overflows i32")
101+ })?;
102+ parcel.write::<i32>(&count).map_err(ipc_err_handle)?;
103+ for info in infos {
104+ serialize_operation_info(info, parcel)?;
105+ }
106+ Ok(())
107+}
108+ 
109+/// Deserialize RemoteControlParams from MsgParcel.
110+pub fn deserialize_remote_control_params(parcel: &mut MsgParcel) -> Result<RemoteControlParams> {
111+ let challenge = parcel.read_string16().map_err(ipc_err_handle)?;
112+ let remote_control_ticket = parcel.read_string16().map_err(ipc_err_handle)?;
113+ let controlled_device_name = parcel.read_string16().map_err(ipc_err_handle)?;
114+ let controller_device_name = parcel.read_string16().map_err(ipc_err_handle)?;
115+ let sign_verify_msg = parcel.read_string16().map_err(ipc_err_handle)?;
116+ Ok(RemoteControlParams {
117+ challenge,
118+ remote_control_ticket,
119+ controlled_device_name,
120+ controller_device_name,
121+ sign_verify_msg,
122+ })
123+}
124+ 
125+/// Serialize RemoteControlParams to MsgParcel.
126+pub fn serialize_remote_control_params(
127+ params: &RemoteControlParams,
128+ parcel: &mut MsgParcel,
129+) -> Result<()> {
130+ parcel.write_string16(&params.challenge).map_err(ipc_err_handle)?;
131+ parcel.write_string16(&params.remote_control_ticket).map_err(ipc_err_handle)?;
132+ parcel.write_string16(&params.controlled_device_name).map_err(ipc_err_handle)?;
133+ parcel.write_string16(&params.controller_device_name).map_err(ipc_err_handle)?;
134+ parcel.write_string16(&params.sign_verify_msg).map_err(ipc_err_handle)?;
135+ Ok(())
136+}
137+ 
138+/// Deserialize RemoteInfo from MsgParcel.
139+pub fn deserialize_remote_info(parcel: &mut MsgParcel) -> Result<RemoteInfo> {
140+ let role_val = parcel.read::<u32>().map_err(ipc_err_handle)?;
141+ let role = match role_val {
142+ 1 => Role::Controller,
143+ 2 => Role::Controlled,
144+ _ => return macros_lib::log_throw_error!(
145+ ErrCode::InvalidArgument,
146+ "[FATAL][IPC]Invalid role: {}",
147+ role_val
148+ ),
149+ };
150+ let remote_id = parcel.read_string16().map_err(ipc_err_handle)?;
151+ let domain_id = parcel.read_string16().map_err(ipc_err_handle)?;
152+ let remote_control_params = deserialize_remote_control_params(parcel)?;
153+ Ok(RemoteInfo {
154+ role,
155+ remote_id,
156+ domain_id,
157+ remote_control_params,
158+ })
159+}
160+ 
161+/// Serialize RemoteInfo to MsgParcel.
162+pub fn serialize_remote_info(info: &RemoteInfo, parcel: &mut MsgParcel) -> Result<()> {
163+ parcel.write::<u32>(&(info.role as u32)).map_err(ipc_err_handle)?;
164+ parcel.write_string16(&info.remote_id).map_err(ipc_err_handle)?;
165+ parcel.write_string16(&info.domain_id).map_err(ipc_err_handle)?;
166+ serialize_remote_control_params(&info.remote_control_params, parcel)?;
167+ Ok(())
168+}
169+ 
170+/// Deserialize PermissionQuery from MsgParcel.
171+pub fn deserialize_permission_query(parcel: &mut MsgParcel) -> Result<PermissionQuery> {
172+ let operation_info = deserialize_operation_infos(parcel)?;
173+ let need_ticket = parcel.read::<bool>().map_err(ipc_err_handle)?;
174+ let ticket_expire_time_ms = parcel.read::<i32>().map_err(ipc_err_handle)?;
175+ let caller_token_id = parcel.read::<i32>().map_err(ipc_err_handle)?;
176+ let domain_id = parcel.read_string16().map_err(ipc_err_handle)?;
177+ let remote_info = deserialize_remote_info(parcel)?;
178+ Ok(PermissionQuery {
179+ operation_info,
180+ need_ticket,
181+ ticket_expire_time_ms,
182+ caller_token_id,
183+ domain_id,
184+ remote_info,
185+ })
186+}
187+ 
188+/// Deserialize PermissionQuery vector from MsgParcel.
189+pub fn deserialize_permission_queries(parcel: &mut MsgParcel) -> Result<Vec<PermissionQuery>> {
190+ let count = parcel.read::<i32>().map_err(ipc_err_handle)?;
191+ if count < 0 || count as u32 > MAX_TICKET_CAPACITY {
192+ return macros_lib::log_throw_error!(
193+ ErrCode::InvalidArrayLen,
194+ "[FATAL][IPC]PermissionQuery vector size invalid: {}",
195+ count
196+ );
197+ }
198+ let mut vec = Vec::with_capacity(count as usize);
199+ for _ in 0..count {
200+ vec.push(deserialize_permission_query(parcel)?);
201+ }
202+ Ok(vec)
203+}
204+ 
205+/// Deserialize RemoteAuthPackage from MsgParcel.
206+pub fn deserialize_remote_auth_package(parcel: &mut MsgParcel) -> Result<RemoteAuthPackage> {
207+ let remote_message_str = parcel.read_string16().map_err(ipc_err_handle)?;
208+ let remote_message = remote_message_wrapper::deserialize_remote_message_from_json(&remote_message_str)?;
209+ let challenge = parcel.read_string16().map_err(ipc_err_handle)?;
210+ let ticket = parcel.read_string16().map_err(ipc_err_handle)?;
211+ Ok(RemoteAuthPackage { remote_message, challenge, ticket })
212+}
213+ 
214+/// Serialize RemoteAuthPackage to MsgParcel.
215+pub fn serialize_remote_auth_package(package: &RemoteAuthPackage, parcel: &mut MsgParcel) -> Result<()> {
216+ let remote_message_str = remote_message_wrapper::serialize_remote_message_to_json(&package.remote_message)?;
217+ parcel.write_string16(&remote_message_str).map_err(ipc_err_handle)?;
218+ parcel.write_string16(&package.challenge).map_err(ipc_err_handle)?;
219+ parcel.write_string16(&package.ticket).map_err(ipc_err_handle)?;
220+ Ok(())
221+}
222+ 
223+/// Deserialize RemoteAuthPackage vector from MsgParcel.
224+pub fn deserialize_remote_auth_packages(parcel: &mut MsgParcel) -> Result<Vec<RemoteAuthPackage>> {
225+ let count = parcel.read::<i32>().map_err(ipc_err_handle)?;
226+ if count < 0 || count as u32 > MAX_TICKET_CAPACITY {
227+ return macros_lib::log_throw_error!(
228+ ErrCode::InvalidArrayLen,
229+ "[FATAL][IPC]RemoteAuthPackage vector size invalid: {}",
230+ count
231+ );
232+ }
233+ let mut vec = Vec::with_capacity(count as usize);
234+ for _ in 0..count {
235+ vec.push(deserialize_remote_auth_package(parcel)?);
236+ }
237+ Ok(vec)
238+}
239+ 
240+/// Serialize RemoteAuthPackage vector to MsgParcel.
241+pub fn serialize_remote_auth_packages(packages: &Vec<RemoteAuthPackage>, parcel: &mut MsgParcel) -> Result<()> {
242+ if packages.len() as u32 > MAX_TICKET_CAPACITY {
243+ return macros_lib::log_throw_error!(
244+ ErrCode::InvalidArrayLen,
245+ "[FATAL][IPC]RemoteAuthPackage vector size exceeds limit: {}",
246+ packages.len()
247+ );
248+ }
249+ let count = i32::try_from(packages.len()).map_err(|_| {
250+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidArrayLen,
251+ "[FATAL][IPC]RemoteAuthPackage vector length overflows i32")
252+ })?;
253+ parcel.write::<i32>(&count).map_err(ipc_err_handle)?;
254+ for package in packages {
255+ serialize_remote_auth_package(package, parcel)?;
256+ }
257+ Ok(())
258+}
259+ 
260+/// Serialize bool vector to MsgParcel (for reply).
261+pub fn serialize_bool_vec(vec: &Vec<bool>, parcel: &mut MsgParcel) -> Result<()> {
262+ if vec.len() as u32 > MAX_VEC_CAPACITY {
263+ return macros_lib::log_throw_error!(
264+ ErrCode::InvalidArrayLen,
265+ "[FATAL][IPC]bool vector size exceeds limit: {}",
266+ vec.len()
267+ );
268+ }
269+ let count = i32::try_from(vec.len()).map_err(|_| {
270+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidArrayLen,
271+ "[FATAL][IPC]bool vector length overflows i32")
272+ })?;
273+ parcel.write::<i32>(&count).map_err(ipc_err_handle)?;
274+ for val in vec {
275+ parcel.write::<bool>(val).map_err(ipc_err_handle)?;
276+ }
277+ Ok(())
278+}
279+ 
280+/// Deserialize RemoteUserAuthItem from MsgParcel.
281+pub fn deserialize_remote_user_auth_item(parcel: &mut MsgParcel) -> Result<RemoteUserAuthItem> {
282+ let permission = parcel.read_string16().map_err(ipc_err_handle)?;
283+ let auth_result = parcel.read_string16().map_err(ipc_err_handle)?;
284+ Ok(RemoteUserAuthItem {
285+ permission,
286+ auth_result,
287+ })
288+}
289+ 
290+/// Serialize RemoteUserAuthItem to MsgParcel.
291+pub fn serialize_remote_user_auth_item(
292+ item: &RemoteUserAuthItem,
293+ parcel: &mut MsgParcel,
294+) -> Result<()> {
295+ parcel.write_string16(&item.permission).map_err(ipc_err_handle)?;
296+ parcel.write_string16(&item.auth_result).map_err(ipc_err_handle)?;
297+ Ok(())
298+}
299+ 
300+/// Deserialize RemoteUserAuthResults from MsgParcel.
301+pub fn deserialize_remote_user_auth_results(
302+ parcel: &mut MsgParcel,
303+) -> Result<RemoteUserAuthResults> {
304+ let count = parcel.read::<i32>().map_err(ipc_err_handle)?;
305+ if count < 0 || count as u32 > MAX_TICKET_CAPACITY {
306+ return macros_lib::log_throw_error!(
307+ ErrCode::InvalidArrayLen,
308+ "[FATAL][IPC]RemoteUserAuthItem vector size invalid: {}",
309+ count
310+ );
311+ }
312+ let mut results = Vec::with_capacity(count as usize);
313+ for _ in 0..count {
314+ results.push(deserialize_remote_user_auth_item(parcel)?);
315+ }
316+
317+ let permission_query = deserialize_permission_query(parcel)?;
318+
319+ Ok(RemoteUserAuthResults {
320+ results,
321+ permission_query,
322+ })
323+}
324+ 
325+/// Deserialize RemoteUserAuthResults vector from MsgParcel.
326+pub fn deserialize_remote_user_auth_results_vec(
327+ parcel: &mut MsgParcel,
328+) -> Result<Vec<RemoteUserAuthResults>> {
329+ let count = parcel.read::<i32>().map_err(ipc_err_handle)?;
330+ if count < 0 || count as u32 > MAX_TICKET_CAPACITY {
331+ return macros_lib::log_throw_error!(
332+ ErrCode::InvalidArrayLen,
333+ "[FATAL][IPC]RemoteUserAuthResults vector size invalid: {}",
334+ count
335+ );
336+ }
337+ let mut vec = Vec::with_capacity(count as usize);
338+ for _ in 0..count {
339+ vec.push(deserialize_remote_user_auth_results(parcel)?);
340+ }
341+ Ok(vec)
342+}
@@ -0,0 +1,119 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Ticket related serialization functions.
17+ 
18+use ipc::parcel::MsgParcel;
19+use saf_definition::{macros_lib, ErrCode, Result};
20+use saf_definition::{VerifyTicketInfo, CliInfo};
21+use crate::{ipc_err_handle, MAX_TICKET_CAPACITY};
22+ 
23+/// Deserialize VerifyTicketInfo from MsgParcel.
24+pub fn deserialize_verify_ticket_info(parcel: &mut MsgParcel) -> Result<VerifyTicketInfo> {
25+ let message = parcel.read_string16().map_err(ipc_err_handle)?;
26+ let challenge = parcel.read_string16().map_err(ipc_err_handle)?;
27+ let ticket = parcel.read_string16().map_err(ipc_err_handle)?;
28+ Ok(VerifyTicketInfo { message, challenge, ticket })
29+}
30+ 
31+/// Deserialize vector of VerifyTicketInfo from MsgParcel.
32+pub fn deserialize_verify_ticket_infos(parcel: &mut MsgParcel) -> Result<Vec<VerifyTicketInfo>> {
33+ let count = parcel.read::<i32>().map_err(ipc_err_handle)?;
34+ if count < 0 || count as u32 > MAX_TICKET_CAPACITY {
35+ return macros_lib::log_throw_error!(
36+ ErrCode::InvalidArrayLen,
37+ "[FATAL][IPC]VerifyTicketInfo vector size invalid: {}",
38+ count
39+ );
40+ }
41+ let mut vec = Vec::with_capacity(count as usize);
42+ for _ in 0..count {
43+ vec.push(deserialize_verify_ticket_info(parcel)?);
44+ }
45+ Ok(vec)
46+}
47+ 
48+/// Serialize VerifyTicketInfo to MsgParcel (for reply).
49+pub fn serialize_verify_ticket_info(info: &VerifyTicketInfo, parcel: &mut MsgParcel) -> Result<()> {
50+ parcel.write_string16(&info.message).map_err(ipc_err_handle)?;
51+ parcel.write_string16(&info.challenge).map_err(ipc_err_handle)?;
52+ parcel.write_string16(&info.ticket).map_err(ipc_err_handle)?;
53+ Ok(())
54+}
55+ 
56+/// Serialize vector of VerifyTicketInfo to MsgParcel (for reply).
57+pub fn serialize_verify_ticket_infos(infos: &Vec<VerifyTicketInfo>, parcel: &mut MsgParcel) -> Result<()> {
58+ if infos.len() as u32 > MAX_TICKET_CAPACITY {
59+ return macros_lib::log_throw_error!(
60+ ErrCode::InvalidArrayLen,
61+ "[FATAL][IPC]VerifyTicketInfo vector size exceeds limit: {}",
62+ infos.len()
63+ );
64+ }
65+ let count = i32::try_from(infos.len()).map_err(|_| {
66+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidArrayLen,
67+ "[FATAL][IPC]VerifyTicketInfo vector length overflows i32")
68+ })?;
69+ parcel.write::<i32>(&count).map_err(ipc_err_handle)?;
70+ for info in infos {
71+ serialize_verify_ticket_info(info, parcel)?;
72+ }
73+ Ok(())
74+}
75+ 
76+/// Serialize CliInfo to MsgParcel (for reply).
77+pub fn serialize_cli_info(info: &CliInfo, parcel: &mut MsgParcel) -> Result<()> {
78+ parcel.write_string16(&info.caller_token_id).map_err(ipc_err_handle)?;
79+ parcel.write_string16(&info.cli_cmd_name).map_err(ipc_err_handle)?;
80+ parcel.write_string16(&info.sub_cli_cmd_name).map_err(ipc_err_handle)?;
81+ serialize_string_vec(&info.permission_list, parcel)?;
82+ Ok(())
83+}
84+ 
85+/// Serialize vector of CliInfo to MsgParcel (for reply).
86+pub fn serialize_cli_infos(infos: &Vec<CliInfo>, parcel: &mut MsgParcel) -> Result<()> {
87+ write_vec_len(infos.len(), MAX_TICKET_CAPACITY as i32, ErrCode::InvalidArrayLen,
88+ "CliInfo", parcel)?;
89+ for info in infos {
90+ serialize_cli_info(info, parcel)?;
91+ }
92+ Ok(())
93+}
94+ 
95+/// Serialize string vector to MsgParcel (for reply).
96+pub fn serialize_string_vec(vec: &Vec<String>, parcel: &mut MsgParcel) -> Result<()> {
97+ write_vec_len(vec.len(), MAX_TICKET_CAPACITY as i32, ErrCode::InvalidArrayLen,
98+ "string", parcel)?;
99+ for s in vec {
100+ parcel.write_string16(s).map_err(ipc_err_handle)?;
101+ }
102+ Ok(())
103+}
104+ 
105+/// Write vector length to parcel with capacity check.
106+pub fn write_vec_len(vec_len: usize, max_capacity: i32, err_code: ErrCode, label: &str,
107+ parcel: &mut MsgParcel) -> Result<()> {
108+ if vec_len > max_capacity as usize {
109+ return macros_lib::log_throw_error!(
110+ err_code,
111+ "[FATAL][IPC]{} vector size exceeds limit: {}",
112+ label,
113+ vec_len
114+ );
115+ }
116+ let count = vec_len as i32;
117+ parcel.write::<i32>(&count).map_err(ipc_err_handle)?;
118+ Ok(())
119+}
@@ -54,6 +54,31 @@ class GrantPermissionsContext : public AgentFenceAsyncContext {
54 std::vector<UserAuthResult> userAuthResult {};54 std::vector<UserAuthResult> userAuthResult {};
55 std::vector<VerifyTicketInfo> ticketInfo {};55 std::vector<VerifyTicketInfo> ticketInfo {};
56};56};
57+ 
58+class GenerateControlledDevicePackageContext : public AgentFenceAsyncContext {
59+public:
60+ std::vector<PermissionQuery> permissionQuery {};
61+ std::vector<RemoteAuthPackage> remoteAuthPackage {};
62+};
63+ 
64+class VerifyControlledDevicePackageContext : public AgentFenceAsyncContext {
65+public:
66+ std::vector<RemoteAuthPackage> ticketInfo {};
67+ std::vector<bool> verifyRes {};
68+};
69+ 
70+class GenerateControllerDevicePackageContext : public AgentFenceAsyncContext {
71+public:
72+ std::vector<RemoteUserAuthResults> remoteUserAuthResults {};
73+ std::vector<RemoteAuthPackage> remoteAuthPackage {};
74+};
75+ 
76+class VerifyControllerDevicePackageContext : public AgentFenceAsyncContext {
77+public:
78+ std::vector<RemoteAuthPackage> ticketInfo {};
79+ RemoteInfo remoteInfo {};
80+ std::vector<bool> verifyRes {};
81+};
57} // namespace SAF82} // namespace SAF
58} // namespace Security83} // namespace Security
59} // namespace OHOS84} // namespace OHOS
@@ -28,6 +28,7 @@ using namespace OHOS::Security::SAF;
28using namespace OHOS::Security::SAF_ASSET_COMMON;28using namespace OHOS::Security::SAF_ASSET_COMMON;
29 29 
30namespace {30namespace {
31+constexpr size_t VERIFY_CONTROLLER_PACKAGE_ARG_COUNT = 2;
31napi_value NapiRequestToolPermissions(const napi_env env, napi_callback_info info)32napi_value NapiRequestToolPermissions(const napi_env env, napi_callback_info info)
32{33{
33 auto asyncContext = std::unique_ptr<RequestToolPermissionContext>(new (std::nothrow)RequestToolPermissionContext());34 auto asyncContext = std::unique_ptr<RequestToolPermissionContext>(new (std::nothrow)RequestToolPermissionContext());
@@ -104,6 +105,188 @@ napi_value NapiGrantToolPermissionsByUser(const napi_env env, napi_callback_info
104 return CreateAsyncWork(env, info, std::move(asyncContext), __func__);105 return CreateAsyncWork(env, info, std::move(asyncContext), __func__);
105}106}
106 107 
108+napi_value NapiGenerateControlledDevicePackage(const napi_env env, napi_callback_info info)
109+{
110+ auto asyncContext = std::unique_ptr<GenerateControlledDevicePackageContext>(
111+ new (std::nothrow) GenerateControlledDevicePackageContext());
112+ NAPI_THROW(env, asyncContext == nullptr, COMMON_INTERNAL_ERROR,
113+ "Failed to create GenerateControlledDevicePackageContext");
114+ 
115+ asyncContext->parse = [](napi_env env, napi_callback_info info, AgentFenceAsyncContext *context)
116+ -> napi_status {
117+ GenerateControlledDevicePackageContext *asyncContext =
118+ static_cast<GenerateControlledDevicePackageContext *>(context);
119+ size_t argc = 1;
120+ napi_value argv[1] = { nullptr };
121+ NAPI_CALL_RETURN_ERR(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
122+ NAPI_THROW_RETURN_ERR(env, argc < 1, GENERAL_PARAMETER_ERROR, "Invalid number of arguments");
123+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, argv[0], asyncContext->permissionQuery));
124+ return napi_ok;
125+ };
126+ 
127+ asyncContext->execute = [](napi_env env, void* data) {
128+ GenerateControlledDevicePackageContext *asyncContext =
129+ static_cast<GenerateControlledDevicePackageContext *>(data);
130+ asyncContext->result = SafAgentFence::GenerateControlledDevicePackage(
131+ asyncContext->permissionQuery, asyncContext->remoteAuthPackage);
132+ };
133+ 
134+ asyncContext->resolve = [](napi_env env, AgentFenceAsyncContext *context) -> napi_value {
135+ GenerateControlledDevicePackageContext *asyncContext =
136+ static_cast<GenerateControlledDevicePackageContext *>(context);
137+ napi_value jsResult = nullptr;
138+ NAPI_CALL(env, napi_create_array(env, &jsResult));
139+ for (uint32_t i = 0; i < asyncContext->remoteAuthPackage.size(); ++i) {
140+ napi_value jsResultItem = nullptr;
141+ NAPI_CALL(env, napi_create_object(env, &jsResultItem));
142+ NAPI_CALL(env, NapiSetProperty(env, jsResultItem, "remoteMessage",
143+ asyncContext->remoteAuthPackage[i].remoteMessage));
144+ NAPI_CALL(env, NapiSetProperty(env, jsResultItem, "challenge",
145+ asyncContext->remoteAuthPackage[i].challenge));
146+ NAPI_CALL(env, NapiSetProperty(env, jsResultItem, "ticket",
147+ asyncContext->remoteAuthPackage[i].ticket));
148+ NAPI_CALL(env, napi_set_element(env, jsResult, i, jsResultItem));
149+ }
150+ return jsResult;
151+ };
152+ 
153+ return CreateAsyncWork(env, info, std::move(asyncContext), __func__);
154+}
155+ 
156+napi_value NapiVerifyControlledDevicePackage(const napi_env env, napi_callback_info info)
157+{
158+ auto asyncContext = std::unique_ptr<VerifyControlledDevicePackageContext>(
159+ new (std::nothrow) VerifyControlledDevicePackageContext());
160+ NAPI_THROW(env, asyncContext == nullptr, COMMON_INTERNAL_ERROR,
161+ "Failed to create VerifyControlledDevicePackageContext");
162+ 
163+ asyncContext->parse = [](napi_env env, napi_callback_info info, AgentFenceAsyncContext *context)
164+ -> napi_status {
165+ VerifyControlledDevicePackageContext *asyncContext =
166+ static_cast<VerifyControlledDevicePackageContext *>(context);
167+ size_t argc = 1;
168+ napi_value argv[1] = { nullptr };
169+ NAPI_CALL_RETURN_ERR(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
170+ NAPI_THROW_RETURN_ERR(env, argc < 1, GENERAL_PARAMETER_ERROR, "Invalid number of arguments");
171+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, argv[0], asyncContext->ticketInfo));
172+ return napi_ok;
173+ };
174+ 
175+ asyncContext->execute = [](napi_env env, void* data) {
176+ VerifyControlledDevicePackageContext *asyncContext =
177+ static_cast<VerifyControlledDevicePackageContext *>(data);
178+ asyncContext->result = SafAgentFence::VerifyControlledDevicePackage(
179+ asyncContext->ticketInfo, asyncContext->verifyRes);
180+ };
181+ 
182+ asyncContext->resolve = [](napi_env env, AgentFenceAsyncContext *context) -> napi_value {
183+ VerifyControlledDevicePackageContext *asyncContext =
184+ static_cast<VerifyControlledDevicePackageContext *>(context);
185+ napi_value jsResult = nullptr;
186+ NAPI_CALL(env, napi_create_array(env, &jsResult));
187+ for (uint32_t i = 0; i < asyncContext->verifyRes.size(); ++i) {
188+ napi_value jsResultItem = nullptr;
189+ NAPI_CALL(env, napi_get_boolean(env, asyncContext->verifyRes[i], &jsResultItem));
190+ NAPI_CALL(env, napi_set_element(env, jsResult, i, jsResultItem));
191+ }
192+ return jsResult;
193+ };
194+ 
195+ return CreateAsyncWork(env, info, std::move(asyncContext), __func__);
196+}
197+ 
198+napi_value NapiGenerateControllerDevicePackage(const napi_env env, napi_callback_info info)
199+{
200+ auto asyncContext = std::unique_ptr<GenerateControllerDevicePackageContext>(
201+ new (std::nothrow) GenerateControllerDevicePackageContext());
202+ NAPI_THROW(env, asyncContext == nullptr, COMMON_INTERNAL_ERROR,
203+ "Failed to create GenerateControllerDevicePackageContext");
204+ 
205+ asyncContext->parse = [](napi_env env, napi_callback_info info, AgentFenceAsyncContext *context)
206+ -> napi_status {
207+ GenerateControllerDevicePackageContext *asyncContext =
208+ static_cast<GenerateControllerDevicePackageContext *>(context);
209+ size_t argc = 1;
210+ napi_value argv[1] = { nullptr };
211+ NAPI_CALL_RETURN_ERR(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
212+ NAPI_THROW_RETURN_ERR(env, argc < 1, GENERAL_PARAMETER_ERROR, "Invalid number of arguments");
213+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, argv[0], asyncContext->remoteUserAuthResults));
214+ return napi_ok;
215+ };
216+ 
217+ asyncContext->execute = [](napi_env env, void* data) {
218+ GenerateControllerDevicePackageContext *asyncContext =
219+ static_cast<GenerateControllerDevicePackageContext *>(data);
220+ asyncContext->result = SafAgentFence::GenerateControllerDevicePackage(
221+ asyncContext->remoteUserAuthResults, asyncContext->remoteAuthPackage);
222+ };
223+ 
224+ asyncContext->resolve = [](napi_env env, AgentFenceAsyncContext *context) -> napi_value {
225+ GenerateControllerDevicePackageContext *asyncContext =
226+ static_cast<GenerateControllerDevicePackageContext *>(context);
227+ napi_value jsResult = nullptr;
228+ NAPI_CALL(env, napi_create_array(env, &jsResult));
229+ for (uint32_t i = 0; i < asyncContext->remoteAuthPackage.size(); ++i) {
230+ napi_value jsResultItem = nullptr;
231+ NAPI_CALL(env, napi_create_object(env, &jsResultItem));
232+ NAPI_CALL(env, NapiSetProperty(env, jsResultItem, "remoteMessage",
233+ asyncContext->remoteAuthPackage[i].remoteMessage));
234+ NAPI_CALL(env, NapiSetProperty(env, jsResultItem, "challenge",
235+ asyncContext->remoteAuthPackage[i].challenge));
236+ NAPI_CALL(env, NapiSetProperty(env, jsResultItem, "ticket",
237+ asyncContext->remoteAuthPackage[i].ticket));
238+ NAPI_CALL(env, napi_set_element(env, jsResult, i, jsResultItem));
239+ }
240+ return jsResult;
241+ };
242+ 
243+ return CreateAsyncWork(env, info, std::move(asyncContext), __func__);
244+}
245+ 
246+napi_value NapiVerifyControllerDevicePackage(const napi_env env, napi_callback_info info)
247+{
248+ auto asyncContext = std::unique_ptr<VerifyControllerDevicePackageContext>(
249+ new (std::nothrow) VerifyControllerDevicePackageContext());
250+ NAPI_THROW(env, asyncContext == nullptr, COMMON_INTERNAL_ERROR,
251+ "Failed to create VerifyControllerDevicePackageContext");
252+ 
253+ asyncContext->parse = [](napi_env env, napi_callback_info info, AgentFenceAsyncContext *context)
254+ -> napi_status {
255+ VerifyControllerDevicePackageContext *asyncContext =
256+ static_cast<VerifyControllerDevicePackageContext *>(context);
257+ size_t argc = VERIFY_CONTROLLER_PACKAGE_ARG_COUNT;
258+ napi_value argv[VERIFY_CONTROLLER_PACKAGE_ARG_COUNT] = { nullptr, nullptr };
259+ NAPI_CALL_RETURN_ERR(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
260+ NAPI_THROW_RETURN_ERR(env, argc < VERIFY_CONTROLLER_PACKAGE_ARG_COUNT, GENERAL_PARAMETER_ERROR,
261+ "Invalid number of arguments");
262+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, argv[0], asyncContext->ticketInfo));
263+ NAPI_CALL_RETURN_ERR(env, NapiGetProperty(env, argv[1], asyncContext->remoteInfo));
264+ return napi_ok;
265+ };
266+ 
267+ asyncContext->execute = [](napi_env env, void* data) {
268+ VerifyControllerDevicePackageContext *asyncContext =
269+ static_cast<VerifyControllerDevicePackageContext *>(data);
270+ asyncContext->result = SafAgentFence::VerifyControllerDevicePackage(
271+ asyncContext->ticketInfo, asyncContext->remoteInfo, asyncContext->verifyRes);
272+ };
273+ 
274+ asyncContext->resolve = [](napi_env env, AgentFenceAsyncContext *context) -> napi_value {
275+ VerifyControllerDevicePackageContext *asyncContext =
276+ static_cast<VerifyControllerDevicePackageContext *>(context);
277+ napi_value jsResult = nullptr;
278+ NAPI_CALL(env, napi_create_array(env, &jsResult));
279+ for (uint32_t i = 0; i < asyncContext->verifyRes.size(); ++i) {
280+ napi_value jsResultItem = nullptr;
281+ NAPI_CALL(env, napi_get_boolean(env, asyncContext->verifyRes[i], &jsResultItem));
282+ NAPI_CALL(env, napi_set_element(env, jsResult, i, jsResultItem));
283+ }
284+ return jsResult;
285+ };
286+ 
287+ return CreateAsyncWork(env, info, std::move(asyncContext), __func__);
288+}
289+ 
107napi_value DeclareOperationType(const napi_env env)290napi_value DeclareOperationType(const napi_env env)
108{291{
109 napi_value status = nullptr;292 napi_value status = nullptr;
@@ -137,11 +320,13 @@ napi_value DeclareRole(const napi_env env)
137napi_value Register(const napi_env env, napi_value exports)320napi_value Register(const napi_env env, napi_value exports)
138{321{
139 napi_property_descriptor desc[] = {322 napi_property_descriptor desc[] = {
140- // register function
141 DECLARE_NAPI_FUNCTION("requestToolPermissions", NapiRequestToolPermissions),323 DECLARE_NAPI_FUNCTION("requestToolPermissions", NapiRequestToolPermissions),
142 DECLARE_NAPI_FUNCTION("grantToolPermissionsByUser", NapiGrantToolPermissionsByUser),324 DECLARE_NAPI_FUNCTION("grantToolPermissionsByUser", NapiGrantToolPermissionsByUser),
325+ DECLARE_NAPI_FUNCTION("generateControlledDevicePackage", NapiGenerateControlledDevicePackage),
326+ DECLARE_NAPI_FUNCTION("verifyControlledDevicePackage", NapiVerifyControlledDevicePackage),
327+ DECLARE_NAPI_FUNCTION("generateControllerDevicePackage", NapiGenerateControllerDevicePackage),
328+ DECLARE_NAPI_FUNCTION("verifyControllerDevicePackage", NapiVerifyControllerDevicePackage),
143 329 
144- // register enum
145 DECLARE_NAPI_PROPERTY("OperationType", DeclareOperationType(env)),330 DECLARE_NAPI_PROPERTY("OperationType", DeclareOperationType(env)),
146 DECLARE_NAPI_PROPERTY("AuthStatus", DeclareAuthStatus(env)),331 DECLARE_NAPI_PROPERTY("AuthStatus", DeclareAuthStatus(env)),
147 DECLARE_NAPI_PROPERTY("Role", DeclareRole(env)),332 DECLARE_NAPI_PROPERTY("Role", DeclareRole(env)),
@@ -0,0 +1,47 @@
1+# Copyright (c) 2026 Huawei Device Co., Ltd.
2+# Licensed under the Apache License, Version 2.0 (the "License");
3+# you may not use this file except in compliance with the License.
4+# You may obtain a copy of the License at
5+#
6+# http://www.apache.org/licenses/LICENSE-2.0
7+#
8+# Unless required by applicable law or agreed to in writing, software
9+# distributed under the License is distributed on an "AS IS" BASIS,
10+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+# See the License for the specific language governing permissions and
12+# limitations under the License.
13+ 
14+import("//build/ohos.gni")
15+ 
16+ohos_rust_static_library("saf_utils") {
17+ sources = [ "src/lib.rs", "src/json_builder.rs" ]
18+
19+ deps = [
20+ "../definition:saf_definition",
21+ "../os_dependency/log:saf_log",
22+ ]
23+ 
24+ external_deps = [ "ylong_json:lib" ]
25+ 
26+rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]
27+ 
28+sanitize = {
29+ integer_overflow = true
30+ cfi = true
31+ debug = false
32+ cfi_cross_dso = true
33+ boundary_sanitize = true
34+ ubsan = true
35+}
36+ 
37+cflags_cc = [
38+ "-Wall",
39+ "-Werror",
40+ "-fPIC",
41+]
42+ 
43+crate_name = "saf_utils"
44+ crate_type = "rlib"
45+ subsystem_name = "security"
46+ part_name = "asset"
47+}
@@ -0,0 +1,24 @@
1+# Copyright (c) 2026 Huawei Device Co., Ltd.
2+# Licensed under the Apache License, Version 2.0 (the "License");
3+# you may not use this file except in compliance with the License.
4+# You may obtain a copy of the License at
5+#
6+# http://www.apache.org/licenses/LICENSE-2.0
7+#
8+# Unless required by applicable law or agreed to in writing, software
9+# distributed under the License is distributed on an "AS IS" BASIS,
10+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+# See the License for the specific language governing permissions and
12+# limitations under the License.
13+ 
14+[package]
15+name = "saf_utils"
16+version = "0.1.0"
17+edition = "2021"
18+ 
19+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
20+ 
21+[dependencies]
22+ylong_json = { git = "https://gitcode.com/openharmony/commonlibrary_rust_ylong_json.git" }
23+saf_definition = { path = "../../definition" }
24+saf_log = { path = "../../os_dependency/log" }
@@ -0,0 +1,219 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! JSON builder module for message construction
17+ 
18+use std::str::FromStr;
19+use ylong_json::{JsonValue, Object, Array, Number};
20+use saf_definition::{macros_lib, ErrCode, Result};
21+ 
22+/// JSON builder for constructing JSON objects with chainable API.
23+pub struct JsonBuilder {
24+ root: Object,
25+}
26+ 
27+impl JsonBuilder {
28+ /// Creates a new empty JSON builder.
29+ pub fn new() -> Self {
30+ Self { root: Object::new() }
31+ }
32+ 
33+ /// Adds a string field to the JSON object.
34+ pub fn add_string(&mut self, key: &str, value: &str) {
35+ self.root.insert(key.to_string(), JsonValue::String(value.to_string()));
36+ }
37+ 
38+ /// Adds a signed number field to the JSON object.
39+ pub fn add_number(&mut self, key: &str, value: i64) {
40+ self.root.insert(key.to_string(), JsonValue::Number(Number::Signed(value)));
41+ }
42+ 
43+ /// Adds an unsigned 64-bit number field to the JSON object.
44+ pub fn add_u64(&mut self, key: &str, value: u64) {
45+ self.root.insert(key.to_string(), JsonValue::Number(Number::Unsigned(value)));
46+ }
47+ 
48+ /// Adds a boolean field to the JSON object.
49+ pub fn add_bool(&mut self, key: &str, value: bool) {
50+ self.root.insert(key.to_string(), JsonValue::Boolean(value));
51+ }
52+ 
53+ /// Adds a string array field to the JSON object.
54+ pub fn add_string_array(&mut self, key: &str, values: Vec<String>) {
55+ let mut arr = Array::new();
56+ for v in values {
57+ arr.push(JsonValue::String(v));
58+ }
59+ self.root.insert(key.to_string(), JsonValue::Array(arr));
60+ }
61+ 
62+ /// Adds an object array field to the JSON object.
63+ pub fn add_object_array(&mut self, key: &str, objects: Vec<Object>) {
64+ let mut arr = Array::new();
65+ for obj in objects {
66+ arr.push(JsonValue::Object(obj));
67+ }
68+ self.root.insert(key.to_string(), JsonValue::Array(arr));
69+ }
70+ 
71+ /// Adds a nested object field to the JSON object.
72+ pub fn add_object(&mut self, key: &str, obj: Object) {
73+ self.root.insert(key.to_string(), JsonValue::Object(obj));
74+ }
75+ 
76+ /// Builds and serializes the JSON object to a compact string.
77+ pub fn build(self) -> Result<String> {
78+ let json = JsonValue::Object(self.root);
79+ json.to_compact_string().map_err(|e| {
80+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError, "JSON serialization failed: {}", e)
81+ })
82+ }
83+ 
84+ /// Returns a mutable reference to the root object for advanced operations.
85+ pub fn root(&mut self) -> &mut Object {
86+ &mut self.root
87+ }
88+}
89+ 
90+impl Default for JsonBuilder {
91+ fn default() -> Self {
92+ Self::new()
93+ }
94+}
95+ 
96+/// Creates a new empty JSON object.
97+pub fn new_object() -> Object {
98+ Object::new()
99+}
100+ 
101+/// Adds a string field to a JSON object.
102+pub fn object_add_string(obj: &mut Object, key: &str, value: &str) {
103+ obj.insert(key.to_string(), JsonValue::String(value.to_string()));
104+}
105+ 
106+/// Adds a number field to a JSON object.
107+pub fn object_add_number(obj: &mut Object, key: &str, value: i64) {
108+ obj.insert(key.to_string(), JsonValue::Number(Number::Signed(value)));
109+}
110+ 
111+/// Gets an optional string from a JSON object by key, removing the key from the object.
112+/// Returns empty string if the key doesn't exist or the value is Null.
113+/// Returns error if the key exists but type is not String.
114+pub fn take_optional_string(obj: &mut Object, key: &str) -> Result<String> {
115+ match obj.remove(key) {
116+ Some(JsonValue::String(s)) => Ok(s),
117+ Some(JsonValue::Null) | None => Ok(String::new()),
118+ Some(_) => macros_lib::log_throw_error!(
119+ ErrCode::DataTypeMismatch,
120+ "[FATAL][JSON]{} type mismatch, expected String", key
121+ )
122+ }
123+}
124+ 
125+/// Gets a required string from a JSON object by key, removing the key from the object.
126+/// Returns error if the key doesn't exist, is Null, or type mismatch.
127+pub fn take_required_string(obj: &mut Object, key: &str) -> Result<String> {
128+ match obj.remove(key) {
129+ Some(JsonValue::String(s)) => Ok(s),
130+ Some(JsonValue::Null) | None => macros_lib::log_throw_error!(
131+ ErrCode::ArgEmpty,
132+ "[FATAL][JSON]{} not found or is null", key
133+ ),
134+ Some(_) => macros_lib::log_throw_error!(
135+ ErrCode::DataTypeMismatch,
136+ "[FATAL][JSON]{} type mismatch, expected String", key
137+ ),
138+ }
139+}
140+ 
141+/// Gets a required value from a JSON object by key, removing the key from the object.
142+/// Returns error if the key doesn't exist or is Null.
143+pub fn take_required_value(obj: &mut Object, key: &str) -> Result<JsonValue> {
144+ match obj.remove(key) {
145+ None | Some(JsonValue::Null) => macros_lib::log_throw_error!(
146+ ErrCode::ArgEmpty,
147+ "[FATAL][JSON]{} is null", key
148+ ),
149+ Some(v) => Ok(v),
150+ }
151+}
152+ 
153+/// Converts a JsonValue to Object, returning error if not an object.
154+pub fn json_into_object(value: JsonValue) -> Result<Object> {
155+ match value {
156+ JsonValue::Object(o) => Ok(o),
157+ _ => macros_lib::log_throw_error!(
158+ ErrCode::DataTypeMismatch,
159+ "[FATAL][JSON]value is not an object"
160+ ),
161+ }
162+}
163+ 
164+/// Gets a required JSON value and converts it to compact string representation.
165+/// Returns error if the key doesn't exist or is Null.
166+/// For String values, returns the string directly.
167+/// For other types (Number, Boolean, etc.), converts to compact string representation.
168+pub fn get_compact_json_value(json: &JsonValue, key: &str) -> Result<String> {
169+ match &json[key] {
170+ JsonValue::Null => {
171+ macros_lib::log_throw_error!(
172+ ErrCode::ArgEmpty,
173+ "[FATAL][JSON]{} not found or is null", key
174+ )
175+ }
176+ JsonValue::String(s) => Ok(s.clone()),
177+ value => value.to_compact_string().map_err(|e| {
178+ macros_lib::log_and_into_saf_error!(
179+ ErrCode::JsonParseError,
180+ "[FATAL][JSON]{} to_compact_string failed: {}", key, e
181+ )
182+ }),
183+ }
184+}
185+ 
186+/// template function for get array from json
187+pub fn get_array_from_json<T, F>(json_str: &str, key: &str, convert: F) -> Result<Vec<T>>
188+where
189+ F: Fn(&JsonValue) -> Result<T>,
190+{
191+ let json: JsonValue = JsonValue::from_str(json_str).map_err(|e| {
192+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError, "JSON parse failed: {}", e)
193+ })?;
194+
195+ match &json[key] {
196+ JsonValue::Null => {
197+ macros_lib::log_throw_error!(
198+ ErrCode::ArgEmpty,
199+ "[FATAL][JSON]{} not found or is null", key
200+ )
201+ }
202+ JsonValue::Array(arr) => arr.iter().map(convert).collect(),
203+ _ => macros_lib::log_throw_error!(
204+ ErrCode::DataTypeMismatch,
205+ "[FATAL][JSON]{} type mismatch, expected Array", key
206+ ),
207+ }
208+}
209+ 
210+/// get string array from json
211+pub fn get_string_array_from_json(json_str: &str, key: &str) -> Result<Vec<String>> {
212+ get_array_from_json(json_str, key, |v| match v {
213+ JsonValue::String(s) => Ok(s.clone()),
214+ _ => macros_lib::log_throw_error!(
215+ ErrCode::DataTypeMismatch,
216+ "[FATAL][JSON]array contains non-string element"
217+ ),
218+ })
219+}
@@ -0,0 +1,28 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! This module provides common utilities for SAF.
17+ 
18+mod json_builder;
19+mod time;
20+ 
21+pub use json_builder::{JsonBuilder, new_object, object_add_string, object_add_number,
22+ get_compact_json_value, take_optional_string, take_required_string, take_required_value, json_into_object,
23+ get_string_array_from_json, get_array_from_json};
24+ 
25+pub use time::{system_time_in_millis, system_time_in_seconds};
26+ 
27+// Re-export ylong_json types for convenience
28+pub use ylong_json::{JsonValue, Object};
@@ -0,0 +1,76 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! This module is used to get the system time.
17+ 
18+use std::os::raw::{c_int, c_long};
19+ 
20+use saf_definition::{macros_lib, ErrCode, Result};
21+ 
22+const CLOCK_REALTIME: c_int = 0;
23+const SECS_TO_MILLIS: i64 = 1000;
24+const NANOS_TO_MILLIS: i64 = 1_000_000;
25+ 
26+#[repr(C)]
27+struct Timespec {
28+ tv_sec: c_long,
29+ tv_nsec: c_long,
30+}
31+ 
32+extern "C" {
33+ fn clock_gettime(clock_id: c_int, tp: *mut Timespec) -> c_int;
34+}
35+ 
36+fn get_timespec() -> Result<Timespec> {
37+ let mut ts = Timespec { tv_sec: 0, tv_nsec: 0 };
38+ if unsafe { clock_gettime(CLOCK_REALTIME, &mut ts) } != 0 {
39+ return macros_lib::log_throw_error!(ErrCode::GeneralError, "clock_gettime failed");
40+ }
41+ if ts.tv_sec < 0 {
42+ return macros_lib::log_throw_error!(ErrCode::GeneralError, "32-bit time_t overflow (Y2038)");
43+ }
44+ Ok(ts)
45+}
46+ 
47+/// Get the current time from the system, in milliseconds.
48+pub fn system_time_in_millis() -> Result<u64> {
49+ let ts = get_timespec()?;
50+ let sec_millis = (ts.tv_sec as i64)
51+ .checked_mul(SECS_TO_MILLIS)
52+ .ok_or_else(|| macros_lib::log_and_into_saf_error!(
53+ ErrCode::GeneralError,
54+ "seconds to milliseconds overflow"
55+ ))?;
56+ let nano_millis = ts.tv_nsec as i64 / NANOS_TO_MILLIS;
57+ let millis = sec_millis
58+ .checked_add(nano_millis)
59+ .ok_or_else(|| macros_lib::log_and_into_saf_error!(
60+ ErrCode::GeneralError,
61+ "milliseconds overflow"
62+ ))?;
63+ if millis < 0 {
64+ return macros_lib::log_throw_error!(
65+ ErrCode::GeneralError,
66+ "milliseconds is negative: {}", millis
67+ );
68+ }
69+ Ok(millis as u64)
70+}
71+ 
72+/// Get the current time from the system, in seconds.
73+pub fn system_time_in_seconds() -> Result<u64> {
74+ let ts = get_timespec()?;
75+ Ok(ts.tv_sec as u64)
76+}
@@ -106,8 +106,53 @@ public:
106 static int32_t GrantToolPermissionsByUser(106 static int32_t GrantToolPermissionsByUser(
107 const std::vector<UserAuthResult>& userAuthResult,107 const std::vector<UserAuthResult>& userAuthResult,
108 std::vector<VerifyTicketInfo>& ticketInfo);108 std::vector<VerifyTicketInfo>& ticketInfo);
109+ 
110+ /**
111+ * @brief Generate controlled device packages for remote control scenario.
112+ *
113+ * @param permissionQuery The vector of permission queries.
114+ * @param remoteAuthPackage Output vector of generated remote auth packages.
115+ * @return Returns 0 on success, or error code on failure.
116+ */
117+ static int32_t GenerateControlledDevicePackage(
118+ const std::vector<PermissionQuery> &permissionQuery,
119+ std::vector<RemoteAuthPackage> &remoteAuthPackage);
120+ 
121+ /**
122+ * @brief Verify controlled device packages for remote control scenario.
123+ *
124+ * @param ticketInfo The vector of remote auth packages to verify.
125+ * @param verifyRes Output vector of verification results (boolean).
126+ * @return Returns 0 on success, or error code on failure.
127+ */
128+ static int32_t VerifyControlledDevicePackage(
129+ const std::vector<RemoteAuthPackage> &ticketInfo,
130+ std::vector<bool> &verifyRes);
131+ 
132+ /**
133+ * @brief Generate controller device packages for remote control scenario.
134+ *
135+ * @param remoteUserAuthResults The vector of remote user authentication results.
136+ * @param remoteAuthPackage Output vector of generated remote auth packages.
137+ * @return Returns 0 on success, or error code on failure.
138+ */
139+ static int32_t GenerateControllerDevicePackage(
140+ const std::vector<RemoteUserAuthResults> &remoteUserAuthResults,
141+ std::vector<RemoteAuthPackage> &remoteAuthPackage);
142+ 
143+ /**
144+ * @brief Verify controller device packages for remote control scenario.
145+ *
146+ * @param ticketInfo The vector of remote auth packages to verify.
147+ * @param remoteInfo Remote information for verification.
148+ * @param verifyRes Output vector of verification results (boolean).
149+ * @return Returns 0 on success, or error code on failure.
150+ */
151+ static int32_t VerifyControllerDevicePackage(
152+ const std::vector<RemoteAuthPackage> &ticketInfo,
153+ const RemoteInfo &remoteInfo,
154+ std::vector<bool> &verifyRes);
109};155};
110 156 
111}157}
112- 
113#endif // SAF_AGENT_FENCE_H158#endif // SAF_AGENT_FENCE_H
@@ -97,7 +97,8 @@
97 SAF_ASSIGN_ENUM_VALUE(SAF_ERR_TRUSTED_RING_REMOTE_DEVICE_UNTRUSTED, TRUSTED_RING | 8) \97 SAF_ASSIGN_ENUM_VALUE(SAF_ERR_TRUSTED_RING_REMOTE_DEVICE_UNTRUSTED, TRUSTED_RING | 8) \
98 SAF_ASSIGN_ENUM_VALUE(SAF_ERR_TRUSTED_RING_LACK_OF_AUTH_TOKEN, TRUSTED_RING | 9) \98 SAF_ASSIGN_ENUM_VALUE(SAF_ERR_TRUSTED_RING_LACK_OF_AUTH_TOKEN, TRUSTED_RING | 9) \
99 SAF_ASSIGN_ENUM_VALUE(SAF_ERR_TRUSTED_RING_AUTH_TOKEN_IS_EXPIRED, TRUSTED_RING | 10) \99 SAF_ASSIGN_ENUM_VALUE(SAF_ERR_TRUSTED_RING_AUTH_TOKEN_IS_EXPIRED, TRUSTED_RING | 10) \
100- 100+ /* BMS */ \
101+ SAF_ASSIGN_ENUM_VALUE(SAF_ERR_GET_BUNDLE_NAME_BY_TOKEN_FAILED, BMS | 9) \
101 102 
102#define SAF_EXTRACT_ERR_CODE_MODULE(code) ((code) >> 12)103#define SAF_EXTRACT_ERR_CODE_MODULE(code) ((code) >> 12)
103 104 
@@ -45,6 +45,14 @@ pub enum EventType {
45 45 
46 /// VerifyRemoteTicket46 /// VerifyRemoteTicket
47 VerifyRemoteTicket = 5,47 VerifyRemoteTicket = 5,
48+ /// Sign remote auth package for remote control scenario
49+ SignRemoteAuthPackage = 6,
50+ /// Verify remote auth package for remote control scenario
51+ VerifyRemoteAuthPackage = 7,
52+ /// Store grant record for remote control scenario
53+ StoreGrantRecord = 8,
54+ /// Get device udid for device identification
55+ GetDeviceUdid = 9,
48}56}
49 57 
50/// Performance metrics parameter keys for StatisticsMetrics EventType.58/// Performance metrics parameter keys for StatisticsMetrics EventType.
@@ -108,6 +116,113 @@ pub const VERIFY_REMOTE_TICKET_KEYS: VerifyRemoteTicketKeys =
108 os_account_id: "osAccountId"116 os_account_id: "osAccountId"
109};117};
110 118 
119+/// Sign remote auth package parameter keys for SignRemoteAuthPackage EventType.
120+pub struct SignRemoteAuthPackageKeys {
121+ /// The OS account ID
122+ pub os_account_id: &'static str,
123+ /// The user ID (domainId)
124+ pub uid: &'static str,
125+ /// The remote auth package message
126+ pub remote_auth_package: &'static str,
127+ /// The remote control token
128+ pub remote_control_token: &'static str,
129+ /// The device ID header (output)
130+ pub device_id_header: &'static str,
131+ /// The result remote auth package (output)
132+ pub result_remote_auth_package: &'static str,
133+ /// The sign info (output)
134+ pub sign_info: &'static str,
135+}
136+ 
137+/// SignRemoteAuthPackageKeys constant for SignRemoteAuthPackage EventType.
138+pub const SIGN_REMOTE_AUTH_PACKAGE_KEYS: SignRemoteAuthPackageKeys =
139+ SignRemoteAuthPackageKeys {
140+ os_account_id: "OsAccountId",
141+ uid: "Uid",
142+ remote_auth_package: "RemoteAuthPackage",
143+ remote_control_token: "RemoteControlToken",
144+ device_id_header: "DeviceIdHeader",
145+ result_remote_auth_package: "ResultRemoteAuthPackage",
146+ sign_info: "SignInfo",
147+ };
148+ 
149+/// Verify remote auth package parameter keys for VerifyRemoteAuthPackage EventType.
150+pub struct VerifyRemoteAuthPackageKeys {
151+ /// The OS account ID
152+ pub os_account_id: &'static str,
153+ /// The UID string (domainId)
154+ pub uid: &'static str,
155+ /// The device ID header
156+ pub device_id_header: &'static str,
157+ /// The remote auth message
158+ pub remote_auth_message: &'static str,
159+ /// The sign info
160+ pub sign_info: &'static str,
161+ /// The verify result (output)
162+ pub verify_result: &'static str,
163+}
164+ 
165+/// VerifyRemoteAuthPackageKeys constant for VerifyRemoteAuthPackage EventType.
166+pub const VERIFY_REMOTE_AUTH_PACKAGE_KEYS: VerifyRemoteAuthPackageKeys =
167+ VerifyRemoteAuthPackageKeys {
168+ os_account_id: "OsAccountId",
169+ uid: "Uid",
170+ device_id_header: "DeviceIdHeader",
171+ remote_auth_message: "RemoteAuthMessage",
172+ sign_info: "SignInfo",
173+ verify_result: "VerifyResult",
174+ };
175+ 
176+/// Store grant record parameter keys for StoreGrantRecord EventType.
177+pub struct StoreGrantRecordKeys {
178+ /// The OS account ID
179+ pub os_account_id: &'static str,
180+ /// The controlled device name
181+ pub controlled_device_name: &'static str,
182+ /// The controller device name
183+ pub controller_device_name: &'static str,
184+ /// The is self grant flag
185+ pub is_self_grant: &'static str,
186+ /// The permission names
187+ pub permission_names: &'static str,
188+ /// The device role
189+ pub device_role: &'static str,
190+ /// The calling bundle name
191+ pub calling_bundle_name: &'static str,
192+ /// The grant type
193+ pub grant_type: &'static str,
194+ /// The timestamp
195+ pub timestamp: &'static str,
196+}
197+ 
198+/// StoreGrantRecordKeys constant for StoreGrantRecord EventType.
199+pub const STORE_GRANT_RECORD_KEYS: StoreGrantRecordKeys =
200+ StoreGrantRecordKeys {
201+ os_account_id: "OsAccountId",
202+ controlled_device_name: "ControlledDeviceName",
203+ controller_device_name: "ControllerDeviceName",
204+ is_self_grant: "IsSelfGrant",
205+ permission_names: "PermissionNames",
206+ device_role: "DeviceRole",
207+ calling_bundle_name: "CallingBundleName",
208+ grant_type: "GrantType",
209+ timestamp: "Timestamp",
210+ };
211+ 
212+/// GetDeviceUdid parameter keys for GetDeviceUdid EventType.
213+pub struct GetDeviceUdidKeys {
214+ /// The os account ID (input)
215+ pub os_account_id: &'static str,
216+ /// The device UDID (output)
217+ pub udid: &'static str,
218+}
219+ 
220+/// GetDeviceUdidKeys constant for GetDeviceUdid EventType
221+pub const GET_DEVICE_UDID_KEYS: GetDeviceUdidKeys = GetDeviceUdidKeys {
222+ os_account_id: "osAccountId",
223+ udid: "Udid",
224+};
225+ 
111/// Global constant instance for error metrics parameter keys.226/// Global constant instance for error metrics parameter keys.
112/// Use this to access standardized parameter names for StatisticsError events.227/// Use this to access standardized parameter names for StatisticsError events.
113pub const ERROR_METRICS_KEYS: ErrorMetricsKeys =228pub const ERROR_METRICS_KEYS: ErrorMetricsKeys =
@@ -15,6 +15,7 @@
15 15 
16//! This module defines the interface of the SAF Rust SDK.16//! This module defines the interface of the SAF Rust SDK.
17#![feature(once_cell_try)]17#![feature(once_cell_try)]
18+ 
18pub use saf_definition::*;19pub use saf_definition::*;
19 20 
20use std::sync::{Arc, Mutex, OnceLock};21use std::sync::{Arc, Mutex, OnceLock};
@@ -54,11 +55,11 @@ impl Manager {
54 Ok(Arc::new(Mutex::new(Manager { remote })))55 Ok(Arc::new(Mutex::new(Manager { remote })))
55 }).cloned()56 }).cloned()
56 }57 }
57- 58+
58 /// Check access for certain application.59 /// Check access for certain application.
59- pub fn check_access(&mut self, attributes: &SAFMap) -> Result<()> {60+ pub fn check_access(&mut self, attributes: &SAFMap) -> Result<()> {
60- self.process_one_agr_request(attributes, IpcCode::CheckAccess)?;61+ self.process_one_agr_request(attributes, IpcCode::CheckAccess)?;
61- Ok(())62+ Ok(())
62 }63 }
63 64 
64 fn rebuild(&mut self) -> Result<()> {65 fn rebuild(&mut self) -> Result<()> {
@@ -630,5 +630,5 @@ impl AssetService {
630#[cfg(feature = "AssetTest")]630#[cfg(feature = "AssetTest")]
631/// stub for test631/// stub for test
632pub mod ut_core_service_lib_stub {632pub mod ut_core_service_lib_stub {
633- include!{"../../../test/unittest/ut_test/services/core_service/test_stub/ut_core_service_lib_stub.rs"}633+ include!{"../../../../test/asset/unittest/ut_test/services/core_service/test_stub/ut_core_service_lib_stub.rs"}
634}634}
@@ -184,5 +184,5 @@ pub(crate) fn add(calling_info: &CallingInfo, attributes: &AssetMap) -> Result<(
184#[cfg(feature = "AssetTest")]184#[cfg(feature = "AssetTest")]
185/// stub for test185/// stub for test
186pub mod ut_operation_add_stub {186pub mod ut_operation_add_stub {
187- include!{"../../../../test/unittest/ut_test/services/core_service/test_stub/operations/ut_operation_add_stub.rs"}187+ include!{"../../../../../test/asset/unittest/ut_test/services/core_service/test_stub/operations/ut_operation_add_stub.rs"}
188}188}
@@ -11,6 +11,7 @@
11# See the License for the specific language governing permissions and11# See the License for the specific language governing permissions and
12# limitations under the License.12# limitations under the License.
13 13 
14+import("//base/security/asset/config.gni")
14import("//build/ohos.gni")15import("//build/ohos.gni")
15import("//build/test.gni")16import("//build/test.gni")
16 17 
@@ -19,13 +20,35 @@ ohos_rust_static_library("saf_common") {
19 deps = [20 deps = [
20 "../../../frameworks/secure_access_fence/os_dependency/log:saf_log",21 "../../../frameworks/secure_access_fence/os_dependency/log:saf_log",
21 "../../../frameworks/secure_access_fence/definition:saf_definition",22 "../../../frameworks/secure_access_fence/definition:saf_definition",
23+ "../../../frameworks/secure_access_fence/utils:saf_utils",
22 "../os_dependency:saf_os_dependency",24 "../os_dependency:saf_os_dependency",
23 ]25 ]
24 external_deps = [26 external_deps = [
25 "ipc:ipc_rust",27 "ipc:ipc_rust",
26 "ylong_runtime:ylong_runtime",28 "ylong_runtime:ylong_runtime",
29+ "ylong_json:lib",
27 ]30 ]
28 rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]31 rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]
32+ if (enable_local_test) {
33+ rustflags += [ "--cfg", "feature=\"SAFTest\"" ]
34+ deps += [ "//third_party/rust/crates/lazy-static.rs:lib" ]
35+ }
36+ 
37+ sanitize = {
38+ integer_overflow = true
39+ cfi = true
40+ debug = false
41+ cfi_cross_dso = true
42+ boundary_sanitize = true
43+ ubsan = true
44+ }
45+ 
46+ cflags_cc = [
47+ "-Wall",
48+ "-Werror",
49+ "-fPIC",
50+ ]
51+ 
29 crate_name = "saf_common"52 crate_name = "saf_common"
30 crate_type = "rlib"53 crate_type = "rlib"
31 subsystem_name = "security"54 subsystem_name = "security"
@@ -18,9 +18,15 @@ edition = "2021"
18 18 
19# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html19# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
20 20 
21+[features]
22+SAFTest = ["lazy_static"]
23+ 
21[dependencies]24[dependencies]
22ipc = { git = "https://gitcode.com/openharmony/communication_ipc" }25ipc = { git = "https://gitcode.com/openharmony/communication_ipc" }
23ylong_runtime = { git = "https://gitcode.com/openharmony/commonlibrary_rust_ylong_runtime.git", features = ["full"] }26ylong_runtime = { git = "https://gitcode.com/openharmony/commonlibrary_rust_ylong_runtime.git", features = ["full"] }
27+ylong_json = { git = "https://gitcode.com/openharmony/commonlibrary_rust_ylong_json.git" }
28+lazy_static = { version = "1.0.0", optional = true }
24 29 
25saf_log = { path = "../../../frameworks/secure_access_fence/os_dependency/log" }30saf_log = { path = "../../../frameworks/secure_access_fence/os_dependency/log" }
26saf_definition = { path = "../../../frameworks/secure_access_fence/definition" }31saf_definition = { path = "../../../frameworks/secure_access_fence/definition" }
32+saf_utils = { path = "../../../frameworks/secure_access_fence/utils" }
@@ -22,9 +22,34 @@ pub use counter::{AutoCounter, Counter};
22pub use task_manager::TaskManager;22pub use task_manager::TaskManager;
23pub use calling_info::CallingInfo;23pub use calling_info::CallingInfo;
24 24 
25-use saf_definition::macros_lib;25+// Re-export JsonBuilder from saf_utils
26+pub use saf_utils::{JsonBuilder, new_object, object_add_string, object_add_number};
27+ 
28+#[cfg(not(feature = "SAFTest"))]
29+use saf_definition::{macros_lib, ErrCode, Result};
26use std::convert::TryFrom;30use std::convert::TryFrom;
27 31 
32+#[cfg(not(feature = "SAFTest"))]
33+extern "C" {
34+ fn GetOsAccountIdFromUid(uid: i32, userId: &mut i32) -> bool;
35+}
36+ 
37+/// Calculate user id from uid.
38+#[cfg(not(feature = "SAFTest"))]
39+pub fn get_user_id(uid: u64) -> Result<i32> {
40+ let uid_i32 = i32::try_from(uid)
41+ .map_err(|_| macros_lib::log_and_into_saf_error!(ErrCode::InvalidOsAccountId,
42+ "[FATAL]Uid overflow i32 range"))?;
43+ unsafe {
44+ let mut user_id: i32 = 0;
45+ if GetOsAccountIdFromUid(uid_i32, &mut user_id) {
46+ Ok(user_id)
47+ } else {
48+ macros_lib::log_throw_error!(ErrCode::InvalidOsAccountId, "[FATAL]Get user id failed.")
49+ }
50+ }
51+}
52+ 
28/// The type of the common event.53/// The type of the common event.
29#[repr(C)]54#[repr(C)]
30#[derive(PartialEq, Eq)]55#[derive(PartialEq, Eq)]
@@ -59,3 +84,44 @@ impl TryFrom<&str> for CommonEventType {
59 Ok(CommonEventType::Unknown)84 Ok(CommonEventType::Unknown)
60 }85 }
61}86}
87+ 
88+// ======================== SAFTest mock implementations ========================
89+ 
90+#[cfg(feature = "SAFTest")]
91+use saf_definition::{macros_lib, Result};
92+#[cfg(feature = "SAFTest")]
93+use lazy_static::lazy_static;
94+#[cfg(feature = "SAFTest")]
95+use std::sync::RwLock;
96+ 
97+#[cfg(feature = "SAFTest")]
98+lazy_static! {
99+ static ref MOCK_LOCAL_UDID: RwLock<String> = RwLock::new(String::from("mock_local_udid"));
100+ static ref MOCK_USER_ID: RwLock<i32> = RwLock::new(100);
101+}
102+ 
103+#[cfg(feature = "SAFTest")]
104+pub fn get_user_id(_uid: u64) -> Result<i32> {
105+ Ok(*MOCK_USER_ID.read().unwrap())
106+}
107+ 
108+#[cfg(feature = "SAFTest")]
109+pub fn set_mock_user_id(user_id: i32) {
110+ *MOCK_USER_ID.write().unwrap() = user_id;
111+}
112+ 
113+#[cfg(feature = "SAFTest")]
114+pub fn get_local_udid() -> Result<String> {
115+ Ok(MOCK_LOCAL_UDID.read().unwrap().clone())
116+}
117+ 
118+#[cfg(feature = "SAFTest")]
119+pub fn set_mock_local_udid(udid: &str) {
120+ *MOCK_LOCAL_UDID.write().unwrap() = udid.to_string();
121+}
122+ 
123+#[cfg(feature = "SAFTest")]
124+pub fn reset_mock_local_udid() {
125+ *MOCK_LOCAL_UDID.write().unwrap() = "mock_local_udid".to_string();
126+ *MOCK_USER_ID.write().unwrap() = 100;
127+}
@@ -28,6 +28,7 @@ ohos_static_library("secure_access_fence_wrapper_static") {
28 "${target_gen_dir}",28 "${target_gen_dir}",
29 "${target_gen_dir}/src",29 "${target_gen_dir}/src",
30 "../os_dependency/inc",30 "../os_dependency/inc",
31+ "../../../frameworks/secure_access_fence/base/inc",
31 "../../../frameworks/secure_access_fence/inner_api/agent_fence/inc",32 "../../../frameworks/secure_access_fence/inner_api/agent_fence/inc",
32 "../../../frameworks/secure_access_fence/inner_api/ipc",33 "../../../frameworks/secure_access_fence/inner_api/ipc",
33 "../../../frameworks/secure_access_fence/os_dependency/log/inc",34 "../../../frameworks/secure_access_fence/os_dependency/log/inc",
@@ -90,6 +91,7 @@ ohos_rust_shared_library("secure_access_fence_service") {
90 "../../../frameworks/secure_access_fence/inner_api/ipc:secure_access_fence_interface",91 "../../../frameworks/secure_access_fence/inner_api/ipc:secure_access_fence_interface",
91 "../../../frameworks/secure_access_fence/definition:saf_definition",92 "../../../frameworks/secure_access_fence/definition:saf_definition",
92 "../../../frameworks/secure_access_fence/os_dependency/log:saf_log",93 "../../../frameworks/secure_access_fence/os_dependency/log:saf_log",
94+ "../../../frameworks/secure_access_fence/utils:saf_utils",
93 "../../../interfaces/inner_kits/plugin_interface/secure_access_fence:saf_plugin_interface_rust",95 "../../../interfaces/inner_kits/plugin_interface/secure_access_fence:saf_plugin_interface_rust",
94 "../../../interfaces/inner_kits/rs/secure_access_fence:saf_sdk_rust",96 "../../../interfaces/inner_kits/rs/secure_access_fence:saf_sdk_rust",
95 "../../../interfaces/inner_kits/c/secure_access_fence/agent_fence:saf_agent_fence",97 "../../../interfaces/inner_kits/c/secure_access_fence/agent_fence:saf_agent_fence",
@@ -112,6 +114,10 @@ ohos_rust_shared_library("secure_access_fence_service") {
112 ]114 ]
113 115 
114 rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]116 rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]
117+ if (enable_local_test) {
118+ rustflags += [ "--cfg", "feature=\"SAFTest\"", ]
119+ deps += [ "//third_party/rust/crates/lazy-static.rs:lib" ]
120+ }
115 121 
116 sanitize = {122 sanitize = {
117 integer_overflow = true123 integer_overflow = true
@@ -18,6 +18,9 @@ edition = "2021"
18 18 
19# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html19# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
20 20 
21+[features]
22+SAFTest = ["lazy_static", "saf_common/SAFTest"]
23+ 
21[dependencies]24[dependencies]
22hilog_rust = { git = "https://gitcode.com/openharmony/hiviewdfx_hilog.git" }25hilog_rust = { git = "https://gitcode.com/openharmony/hiviewdfx_hilog.git" }
23samgr = { git = "https://gitcode.com/openharmony/systemabilitymgr_samgr.git" }26samgr = { git = "https://gitcode.com/openharmony/systemabilitymgr_samgr.git" }
@@ -31,7 +34,8 @@ saf_common = { path = "../common" }
31saf_definition = { path = "../../../frameworks/secure_access_fence/definition" }34saf_definition = { path = "../../../frameworks/secure_access_fence/definition" }
32saf_ipc = { path = "../../../frameworks/secure_access_fence/inner_api/ipc" }35saf_ipc = { path = "../../../frameworks/secure_access_fence/inner_api/ipc" }
33saf_log = { path = "../../../frameworks/secure_access_fence/os_dependency/log" }36saf_log = { path = "../../../frameworks/secure_access_fence/os_dependency/log" }
37+saf_utils = { path = "../../../frameworks/secure_access_fence/utils" }
34saf_plugin = { path = "../plugin" }38saf_plugin = { path = "../plugin" }
35saf_plugin_interface = { path = "../../../interfaces/inner_kits/plugin_interface/secure_access_fence" }39saf_plugin_interface = { path = "../../../interfaces/inner_kits/plugin_interface/secure_access_fence" }
36saf_sdk = { path = "../../../interfaces/inner_kits/rs/secure_access_fence" }40saf_sdk = { path = "../../../interfaces/inner_kits/rs/secure_access_fence" }
37-lazy_static = "1.0.0"41+lazy_static = { version = "1.0.0", optional = true }
@@ -842,4 +842,4 @@ int32_t PermissionManager::GetVerifyTicketInfo(const UserAuthResult &userAuthRes
842 "GetVerifyTicketInfo :: GenerateTicketInfoWithTimeStamp failed, ret=%{public}d", ret);842 "GetVerifyTicketInfo :: GenerateTicketInfoWithTimeStamp failed, ret=%{public}d", ret);
843 return SAF_SUCCESS;843 return SAF_SUCCESS;
844}844}
845-}845+}
@@ -174,6 +174,6 @@ int64_t GetBootTimeMs()
174 return TimeWrapper::GetBootTimeMs();174 return TimeWrapper::GetBootTimeMs();
175}175}
176 176 
177-}177+} // namespace SAF
178-}178+} // namespace Security
179-}179+} // namespace OHOS
@@ -28,14 +28,17 @@ use system_ability_fwk::{
28use ylong_runtime::builder::RuntimeBuilder;28use ylong_runtime::builder::RuntimeBuilder;
29 29 
30use saf_common::{Counter, TaskManager};30use saf_common::{Counter, TaskManager};
31-use saf_definition::{macros_lib, ErrCode, Result};31+use saf_definition::{macros_lib, ErrCode, Result, CliInfo, VerifyTicketInfo};
32-use saf_ipc::{CliInfo, VerifyTicketInfo, SA_ID};32+use saf_ipc::SA_ID;
33use saf_log::{logd, loge, logi};33use saf_log::{logd, loge, logi};
34use saf_plugin::saf_plugin::{SAFContext, SAFPlugin};34use saf_plugin::saf_plugin::{SAFContext, SAFPlugin};
35 35 
36+use saf_utils::get_compact_json_value;
37+ 
36use crate::wrapper::{cxx_is_screen_locked, notify_error, notify_performance_metrics};38use crate::wrapper::{cxx_is_screen_locked, notify_error, notify_performance_metrics};
37 39 
38mod common_event;40mod common_event;
41+mod remote_control;
39mod stub;42mod stub;
40mod ticket_operation;43mod ticket_operation;
41mod wrapper;44mod wrapper;
@@ -397,9 +400,9 @@ fn parse_verify_info_json_full(info_str: &str) -> Result<(VerifyTicketInfo, ylon
397 return Err(macros_lib::log_and_into_saf_error!(ErrCode::ArgEmpty, "VerifyTicket: json object is empty"));400 return Err(macros_lib::log_and_into_saf_error!(ErrCode::ArgEmpty, "VerifyTicket: json object is empty"));
398 }401 }
399 402 
400- let raw_message = extract_json_string(&json, JSON_KEY_MESSAGE)?;403+ let raw_message = get_compact_json_value(&json, JSON_KEY_MESSAGE)?;
401- let raw_challenge = extract_json_string(&json, JSON_KEY_CHALLENGE)?;404+ let raw_challenge = get_compact_json_value(&json, JSON_KEY_CHALLENGE)?;
402- let raw_ticket = extract_json_string(&json, JSON_KEY_TICKET)?;405+ let raw_ticket = get_compact_json_value(&json, JSON_KEY_TICKET)?;
403 406 
404 let message_json = ylong_json::JsonValue::from_text(&raw_message).map_err(|e| {407 let message_json = ylong_json::JsonValue::from_text(&raw_message).map_err(|e| {
405 loge!("VerifyTicket: message json parse failed: {}", e);408 loge!("VerifyTicket: message json parse failed: {}", e);
@@ -409,19 +412,6 @@ fn parse_verify_info_json_full(info_str: &str) -> Result<(VerifyTicketInfo, ylon
409 Ok((VerifyTicketInfo { message: raw_message, challenge: raw_challenge, ticket: raw_ticket }, message_json))412 Ok((VerifyTicketInfo { message: raw_message, challenge: raw_challenge, ticket: raw_ticket }, message_json))
410}413}
411 414 
412-fn extract_json_string(json: &ylong_json::JsonValue, key: &str) -> Result<String> {
413- let value = &json[key];
414- if value == &ylong_json::JsonValue::Null {
415- return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "{} field missing", key);
416- }
417- match value {
418- ylong_json::JsonValue::String(s) => Ok(s.clone()),
419- _ => value.to_compact_string().map_err(|e| {
420- macros_lib::SAFError::new(ErrCode::ArgEmpty, format!("VerifyTicket: {} extract failed: {}", key, e))
421- }),
422- }
423-}
424- 
425fn extract_cli_infos_with_json(message_json: &ylong_json::JsonValue) -> Result<Vec<CliInfo>> {415fn extract_cli_infos_with_json(message_json: &ylong_json::JsonValue) -> Result<Vec<CliInfo>> {
426 if message_json == &ylong_json::JsonValue::Null {416 if message_json == &ylong_json::JsonValue::Null {
427 return macros_lib::log_throw_error!(417 return macros_lib::log_throw_error!(
@@ -430,7 +420,7 @@ fn extract_cli_infos_with_json(message_json: &ylong_json::JsonValue) -> Result<V
430 );420 );
431 }421 }
432 422 
433- let caller_token_id = extract_json_string(message_json, JSON_KEY_CALLER_TOKEN_ID)?;423+ let caller_token_id = get_compact_json_value(message_json, JSON_KEY_CALLER_TOKEN_ID)?;
434 424 
435 let cli_infos_array = &message_json[JSON_KEY_CLI_INFOS];425 let cli_infos_array = &message_json[JSON_KEY_CLI_INFOS];
436 if cli_infos_array == &ylong_json::JsonValue::Null {426 if cli_infos_array == &ylong_json::JsonValue::Null {
@@ -444,9 +434,9 @@ fn extract_cli_infos_with_json(message_json: &ylong_json::JsonValue) -> Result<V
444 let mut result = Vec::with_capacity(arr.len());434 let mut result = Vec::with_capacity(arr.len());
445 for item in arr.iter() {435 for item in arr.iter() {
446 // ATM不解析这个json, 且ATM需要不带双引号的字符串,在此处帮助ATM删除双引号436 // ATM不解析这个json, 且ATM需要不带双引号的字符串,在此处帮助ATM删除双引号
447- let cli_cmd_name = extract_json_string(item, JSON_KEY_CLI_CMD_NAME)?.trim_matches(STRING_QUOTE).to_string();437+ let cli_cmd_name = get_compact_json_value(item, JSON_KEY_CLI_CMD_NAME)?.trim_matches(STRING_QUOTE).to_string();
448 let sub_cli_cmd_name =438 let sub_cli_cmd_name =
449- extract_json_string(item, JSON_KEY_SUB_CLI_CMD_NAME)?.trim_matches(STRING_QUOTE).to_string();439+ get_compact_json_value(item, JSON_KEY_SUB_CLI_CMD_NAME)?.trim_matches(STRING_QUOTE).to_string();
450 let permission_list = extract_permission_list(item)?;440 let permission_list = extract_permission_list(item)?;
451 result.push(CliInfo {441 result.push(CliInfo {
452 caller_token_id: caller_token_id.clone(),442 caller_token_id: caller_token_id.clone(),
@@ -481,5 +471,5 @@ fn extract_permission_list(json: &ylong_json::JsonValue) -> Result<Vec<String>>
481#[cfg(feature = "SAFTest")]471#[cfg(feature = "SAFTest")]
482/// stub for test472/// stub for test
483pub mod ut_core_service_lib_stub {473pub mod ut_core_service_lib_stub {
484- include! {"../../../test/unittest/ut_test/services/core_service/test_stub/ut_core_service_lib_stub.rs"}474+ include! {"../../../../test/secure_access_fence/unittest/ut_test/services/core_service/test_stub/ut_core_service_lib_stub.rs"}
485}475}
@@ -0,0 +1,284 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Trusted ring adapter for remote auth package operations
17+ 
18+use saf_definition::{macros_lib, ErrCode, Result};
19+#[cfg(not(feature = "SAFTest"))]
20+use saf_log::logi;
21+use saf_definition::{DeviceIdHeader, RemoteAuthPackage};
22+ 
23+#[cfg(not(feature = "SAFTest"))]
24+use std::collections::HashMap;
25+#[cfg(not(feature = "SAFTest"))]
26+use saf_definition::Value;
27+#[cfg(not(feature = "SAFTest"))]
28+use saf_plugin::saf_plugin::SAFPlugin;
29+#[cfg(not(feature = "SAFTest"))]
30+use saf_plugin_interface::plugin_interface::{
31+ EventType, ExtMap,
32+ SIGN_REMOTE_AUTH_PACKAGE_KEYS, VERIFY_REMOTE_AUTH_PACKAGE_KEYS,
33+};
34+#[cfg(not(feature = "SAFTest"))]
35+use saf_ipc::remote_message_wrapper;
36+#[cfg(not(feature = "SAFTest"))]
37+use saf_utils::{JsonValue, json_into_object, get_compact_json_value};
38+ 
39+#[cfg(not(feature = "SAFTest"))]
40+fn log_sign_params(params: &SignParams) {
41+ logi!(
42+ "SignParams: os_account_id={}, uid_len={}, remote_auth_package_len={}, remote_control_token_len={}",
43+ params.os_account_id,
44+ params.uid.len(),
45+ params.remote_auth_package.len(),
46+ params.remote_control_token.len()
47+ );
48+}
49+ 
50+#[cfg(not(feature = "SAFTest"))]
51+fn log_sign_result(result: &SignResult) {
52+ logi!(
53+ "SignResult: controller_device_id_len={}, controlled_device_id_len={}, \
54+ remote_auth_package_len={}, sign_info_len={}",
55+ result.device_id_header.controller_device_id.len(),
56+ result.device_id_header.controlled_device_id.len(),
57+ result.remote_auth_package.len(),
58+ result.sign_info.len()
59+ );
60+}
61+ 
62+#[cfg(not(feature = "SAFTest"))]
63+fn log_verify_package(package: &RemoteAuthPackage) {
64+ let remote_message = &package.remote_message;
65+ let device_info = &remote_message.device_info;
66+ logi!(
67+ "RemoteAuthPackage: ticket_len={}, \
68+ controller_device_id_len={}, controlled_device_id_len={}, \
69+ remote_auth_message_len={}",
70+ package.ticket.len(),
71+ device_info.controller_device_id.len(),
72+ device_info.controlled_device_id.len(),
73+ remote_message.remote_auth_message.len()
74+ );
75+}
76+ 
77+/// Sign request parameters.
78+pub struct SignParams {
79+ /// OS account ID.
80+ pub os_account_id: i32,
81+ /// UID string.
82+ pub uid: String,
83+ /// Remote auth package.
84+ pub remote_auth_package: String,
85+ /// Remote control token.
86+ pub remote_control_token: String,
87+}
88+ 
89+/// Sign result.
90+pub struct SignResult {
91+ /// Device ID header.
92+ pub device_id_header: DeviceIdHeader,
93+ /// Remote auth package.
94+ pub remote_auth_package: String,
95+ /// Sign info.
96+ pub sign_info: String,
97+}
98+ 
99+#[cfg(not(feature = "SAFTest"))]
100+fn parse_sign_result(mut ret: ExtMap) -> Result<SignResult> {
101+ let device_id_header_str = match ret.remove(SIGN_REMOTE_AUTH_PACKAGE_KEYS.device_id_header) {
102+ Some(Value::String(ref s)) => s.clone(),
103+ _ => return macros_lib::log_throw_error!(ErrCode::HashMapKeyNotFound, "device_id_header not found"),
104+ };
105+ 
106+ let mut device_id_header_obj = json_into_object(JsonValue::from_text(device_id_header_str)
107+ .map_err(|e| macros_lib::log_and_into_saf_error!(
108+ ErrCode::JsonParseError, "parse device_id_header failed: {}", e))?)?;
109+ 
110+ let device_id_header = remote_message_wrapper::parse_device_id_header_from_json(&mut device_id_header_obj)?;
111+ 
112+ let remote_auth_package = match ret.remove(SIGN_REMOTE_AUTH_PACKAGE_KEYS.result_remote_auth_package) {
113+ Some(Value::String(ref s)) => s.clone(),
114+ None => return macros_lib::log_throw_error!(ErrCode::HashMapKeyNotFound,
115+ "result_remote_auth_package not found"),
116+ _ => return macros_lib::log_throw_error!(ErrCode::DataTypeMismatch,
117+ "result_remote_auth_package type mismatch, expected String"),
118+ };
119+
120+ let sign_info = match ret.remove(SIGN_REMOTE_AUTH_PACKAGE_KEYS.sign_info) {
121+ Some(Value::String(ref s)) => s.clone(),
122+ None => return macros_lib::log_throw_error!(ErrCode::HashMapKeyNotFound,
123+ "sign_info not found"),
124+ _ => return macros_lib::log_throw_error!(ErrCode::DataTypeMismatch,
125+ "sign_info type mismatch, expected String"),
126+ };
127+ 
128+ Ok(SignResult {
129+ device_id_header,
130+ remote_auth_package,
131+ sign_info,
132+ })
133+}
134+ 
135+/// Signs remote auth package through trusted ring plugin.
136+#[cfg(not(feature = "SAFTest"))]
137+pub fn sign_remote_auth_package(params: SignParams) -> Result<SignResult> {
138+ log_sign_params(&params);
139+ 
140+ let loader = SAFPlugin::get_instance().load_plugin().map_err(|e| {
141+ macros_lib::log_and_into_saf_error!(ErrCode::PluginNotSupport, "load plugin failed: {}", e)
142+ })?;
143+ 
144+ let mut ext_map: ExtMap = HashMap::new();
145+ let os_account_id_u32 = u32::try_from(params.os_account_id).map_err(|_| {
146+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidOsAccountId,
147+ "os_account_id is negative: {}", params.os_account_id)
148+ })?;
149+ ext_map.insert(SIGN_REMOTE_AUTH_PACKAGE_KEYS.os_account_id,
150+ Value::Number(os_account_id_u32));
151+ ext_map.insert(SIGN_REMOTE_AUTH_PACKAGE_KEYS.uid,
152+ Value::String(params.uid));
153+ ext_map.insert(SIGN_REMOTE_AUTH_PACKAGE_KEYS.remote_auth_package,
154+ Value::String(params.remote_auth_package));
155+ ext_map.insert(SIGN_REMOTE_AUTH_PACKAGE_KEYS.remote_control_token,
156+ Value::String(params.remote_control_token));
157+ 
158+ let ret = loader.process_event(EventType::SignRemoteAuthPackage, &mut ext_map)
159+ .map_err(|e| {
160+ macros_lib::log_and_into_saf_error!(
161+ ErrCode::try_from(e).unwrap_or(ErrCode::GeneralError),
162+ "sign_remote_auth_package process_event failed")
163+ })?;
164+ 
165+ let result = parse_sign_result(ret)?;
166+ log_sign_result(&result);
167+ Ok(result)
168+}
169+ 
170+/// Verifies remote auth package through trusted ring plugin.
171+#[cfg(not(feature = "SAFTest"))]
172+pub fn verify_remote_auth_package(os_account_id: i32, package: &RemoteAuthPackage) -> Result<bool> {
173+ log_verify_package(package);
174+ 
175+ let loader = SAFPlugin::get_instance().load_plugin().map_err(|e| {
176+ macros_lib::log_and_into_saf_error!(ErrCode::PluginNotSupport, "load plugin failed: {}", e)
177+ })?;
178+ 
179+ let device_id_header_str = remote_message_wrapper::serialize_device_id_header_to_json(
180+ &package.remote_message.device_info
181+ )?;
182+
183+ let uid = parse_uid_from_remote_auth_message(&package.remote_message.remote_auth_message)?;
184+
185+ let mut ext_map: ExtMap = HashMap::new();
186+ let os_account_id_u32 = u32::try_from(os_account_id).map_err(|_| {
187+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidOsAccountId,
188+ "os_account_id is negative: {}", os_account_id)
189+ })?;
190+ ext_map.insert(VERIFY_REMOTE_AUTH_PACKAGE_KEYS.os_account_id,
191+ Value::Number(os_account_id_u32));
192+ ext_map.insert(VERIFY_REMOTE_AUTH_PACKAGE_KEYS.uid,
193+ Value::String(uid));
194+ ext_map.insert(VERIFY_REMOTE_AUTH_PACKAGE_KEYS.device_id_header,
195+ Value::String(device_id_header_str));
196+ ext_map.insert(VERIFY_REMOTE_AUTH_PACKAGE_KEYS.remote_auth_message,
197+ Value::String(package.remote_message.remote_auth_message.clone()));
198+ ext_map.insert(VERIFY_REMOTE_AUTH_PACKAGE_KEYS.sign_info,
199+ Value::String(package.ticket.clone()));
200+ 
201+ let ret = loader.process_event(EventType::VerifyRemoteAuthPackage, &mut ext_map)
202+ .map_err(|e| {
203+ macros_lib::log_and_into_saf_error!(
204+ ErrCode::try_from(e).unwrap_or(ErrCode::GeneralError),
205+ "verify_remote_auth_package process_event failed")
206+ })?;
207+ 
208+ match ret.get(VERIFY_REMOTE_AUTH_PACKAGE_KEYS.verify_result) {
209+ Some(Value::Bool(b)) => {
210+ logi!("[verify_remote_auth_package] verify_result={}", b);
211+ Ok(*b)
212+ },
213+ _ => macros_lib::log_throw_error!(ErrCode::HashMapKeyNotFound, "verify_result not found"),
214+ }
215+}
216+ 
217+#[cfg(not(feature = "SAFTest"))]
218+fn parse_uid_from_remote_auth_message(remote_auth_message: &str) -> Result<String> {
219+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
220+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
221+ "parse remote_auth_message failed: {}", e)
222+ })?;
223+
224+ let uid = get_compact_json_value(&json, "domainId")
225+ .map(|s| s.trim_matches('"').to_string())
226+ .unwrap_or_default();
227+
228+ if uid.is_empty() {
229+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "uid is empty in remote_auth_message");
230+ }
231+
232+ Ok(uid)
233+}
234+ 
235+// ======================== SAFTest mock implementations ========================
236+ 
237+#[cfg(feature = "SAFTest")]
238+use std::sync::atomic::{AtomicBool, Ordering};
239+#[cfg(feature = "SAFTest")]
240+use lazy_static::lazy_static;
241+ 
242+#[cfg(feature = "SAFTest")]
243+lazy_static! {
244+ static ref MOCK_SIGN_SUCCESS: AtomicBool = AtomicBool::new(true);
245+ static ref MOCK_VERIFY_SUCCESS: AtomicBool = AtomicBool::new(true);
246+}
247+ 
248+#[cfg(feature = "SAFTest")]
249+pub fn sign_remote_auth_package(params: SignParams) -> Result<SignResult> {
250+ if MOCK_SIGN_SUCCESS.load(Ordering::SeqCst) {
251+ Ok(SignResult {
252+ device_id_header: DeviceIdHeader {
253+ controller_device_id: "mock_controller_udid".to_string(),
254+ controlled_device_id: "mock_controlled_udid".to_string(),
255+ },
256+ remote_auth_package: params.remote_auth_package,
257+ sign_info: "mock_sign_info".to_string(),
258+ })
259+ } else {
260+ macros_lib::log_throw_error!(ErrCode::GeneralError, "Mock sign failed")
261+ }
262+}
263+ 
264+#[cfg(feature = "SAFTest")]
265+pub fn set_mock_sign_success(success: bool) {
266+ MOCK_SIGN_SUCCESS.store(success, Ordering::SeqCst);
267+}
268+ 
269+#[cfg(feature = "SAFTest")]
270+pub fn verify_remote_auth_package(_os_account_id: i32, _package: &RemoteAuthPackage) -> Result<bool> {
271+ Ok(MOCK_VERIFY_SUCCESS.load(Ordering::SeqCst))
272+}
273+ 
274+#[cfg(feature = "SAFTest")]
275+pub fn set_mock_verify_success(success: bool) {
276+ MOCK_VERIFY_SUCCESS.store(success, Ordering::SeqCst);
277+}
278+ 
279+#[cfg(feature = "SAFTest")]
280+pub fn reset_mock_state() {
281+ MOCK_VERIFY_SUCCESS.store(true, Ordering::SeqCst);
282+ MOCK_SIGN_SUCCESS.store(true, Ordering::SeqCst);
283+ crate::remote_control::remote_challenge_manager::reset_mock_challenge_cache();
284+}
@@ -0,0 +1,145 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! CLI tool permission manager adapter
17+ 
18+use saf_definition::{macros_lib, CommandInfo, ErrCode, Result};
19+use saf_log::loge;
20+ 
21+const MAX_PERMISSION_BUF_SIZE: usize = 4096;
22+ 
23+#[repr(C)]
24+struct CxxStr {
25+ ptr: *const std::ffi::c_char,
26+ len: i32,
27+}
28+ 
29+#[repr(C)]
30+struct CxxCmdInfo {
31+ cmd_name: CxxStr,
32+ sub_cmd: CxxStr,
33+}
34+ 
35+#[repr(C)]
36+struct CxxQueryResult {
37+ ret_code: i32,
38+ result_code: i32,
39+ perm_count: i32,
40+}
41+ 
42+extern "C" {
43+ fn CxxBatchQueryCliPermissions(
44+ cmds: *const CxxCmdInfo,
45+ cmd_count: i32,
46+ out_buf: *mut std::ffi::c_char,
47+ buf_size: i32,
48+ out_result: *mut CxxQueryResult,
49+ ) -> i32;
50+}
51+ 
52+/// Batch query CLI tool permissions
53+pub fn batch_query_cli_permission(
54+ cli_infos: &[CommandInfo],
55+ permissions: &mut Vec<String>
56+) -> Result<()> {
57+ if cli_infos.is_empty() {
58+ return Ok(());
59+ }
60+ 
61+ let cxx_cmds = prepare_cxx_cmd_infos(cli_infos);
62+ let mut out_buf = vec![0u8; MAX_PERMISSION_BUF_SIZE];
63+ let mut result = CxxQueryResult {
64+ ret_code: 0,
65+ result_code: 0,
66+ perm_count: 0,
67+ };
68+ 
69+ call_cxx_batch_query(&cxx_cmds, &mut out_buf, &mut result)?;
70+ parse_permissions_from_buffer(&out_buf, result.perm_count, permissions)?;
71+ 
72+ Ok(())
73+}
74+ 
75+fn prepare_cxx_cmd_infos(cli_infos: &[CommandInfo]) -> Vec<CxxCmdInfo> {
76+ cli_infos.iter().map(|cmd| {
77+ CxxCmdInfo {
78+ cmd_name: CxxStr {
79+ ptr: cmd.cmd_name.as_ptr() as *const std::ffi::c_char,
80+ len: cmd.cmd_name.len() as i32,
81+ },
82+ sub_cmd: CxxStr {
83+ ptr: cmd.sub_cmd.as_ptr() as *const std::ffi::c_char,
84+ len: cmd.sub_cmd.len() as i32,
85+ },
86+ }
87+ }).collect()
88+}
89+ 
90+fn call_cxx_batch_query(
91+ cxx_cmds: &[CxxCmdInfo],
92+ out_buf: &mut [u8],
93+ result: &mut CxxQueryResult,
94+) -> Result<()> {
95+ let ret = unsafe {
96+ CxxBatchQueryCliPermissions(
97+ cxx_cmds.as_ptr(),
98+ cxx_cmds.len() as i32,
99+ out_buf.as_mut_ptr() as *mut std::ffi::c_char,
100+ out_buf.len() as i32,
101+ result,
102+ )
103+ };
104+ 
105+ if ret != 0 {
106+ loge!("CxxBatchQueryCliPermissions failed, ret={}, result_code={}", ret, result.result_code);
107+ return macros_lib::log_throw_error!(ErrCode::GeneralError,
108+ "CxxBatchQueryCliPermissions failed");
109+ }
110+ 
111+ Ok(())
112+}
113+ 
114+fn parse_permissions_from_buffer(
115+ out_buf: &[u8],
116+ perm_count: i32,
117+ permissions: &mut Vec<String>,
118+) -> Result<()> {
119+ const MAX_PERMISSION_COUNT: i32 = 200;
120+
121+ if !(0..=MAX_PERMISSION_COUNT).contains(&perm_count) {
122+ return Err(macros_lib::log_and_into_saf_error!(ErrCode::GeneralError,
123+ "Invalid permission count: {}", perm_count));
124+ }
125+
126+ let mut pos = 0;
127+ for _ in 0..perm_count {
128+ if pos >= out_buf.len() {
129+ return Err(macros_lib::log_and_into_saf_error!(ErrCode::GeneralError,
130+ "Permission buffer overflow: pos {} >= buf len {}", pos, out_buf.len()));
131+ }
132+ let end = out_buf[pos..].iter().position(|&b| b == 0).unwrap_or(out_buf.len() - pos);
133+ if pos + end > out_buf.len() {
134+ return Err(macros_lib::log_and_into_saf_error!(ErrCode::GeneralError,
135+ "Permission string exceeds buffer boundary"));
136+ }
137+ let s = std::str::from_utf8(&out_buf[pos..pos + end])
138+ .map_err(|_| macros_lib::log_and_into_saf_error!(ErrCode::GeneralError,
139+ "Invalid UTF-8 in permission"))?;
140+ permissions.push(s.to_string());
141+ pos += end + 1;
142+ }
143+ 
144+ Ok(())
145+}
@@ -0,0 +1,315 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! This module implements controlled device package generation and verification.
17+ 
18+use ipc::Skeleton;
19+use saf_common::{get_user_id, JsonBuilder};
20+use saf_utils::system_time_in_millis;
21+use saf_definition::{macros_lib, ErrCode, Result,
22+ PermissionQuery, RemoteAuthPackage,
23+ Role, RemoteMessage,
24+};
25+use saf_log::{loge, logi};
26+use std::ffi::CString;
27+use std::os::raw::c_char as raw_c_char;
28+ 
29+extern "C" {
30+ fn CheckPermission(permission: *const raw_c_char) -> bool;
31+}
32+ 
33+const QUERY_TOOL_PERMISSIONS: &str = "ohos.permission.QUERY_TOOL_PERMISSIONS";
34+ 
35+use crate::remote_control::{create_empty_package, log_remote_auth_package, parse_ticket_expire_time, parse_timestamp,
36+ validate_ticket_expiration, generate_crypto_random_challenge, serialize_permission_query_to_message,
37+ validate_remote_auth_message_fields, BatchGenerateResult, BatchVerifyResult};
38+use crate::remote_control::remote_challenge_manager::cache_challenge;
39+use crate::remote_control::account_based_auth_manager::{sign_remote_auth_package, verify_remote_auth_package, SignParams};
40+ 
41+/// Generates controlled device packages for permission queries.
42+pub fn generate_controlled_device_package(
43+ queries: Vec<PermissionQuery>
44+) -> BatchGenerateResult {
45+ let permission = CString::new(QUERY_TOOL_PERMISSIONS).unwrap();
46+ if unsafe { !CheckPermission(permission.as_ptr()) } {
47+ loge!("Permission denied! Need {}", QUERY_TOOL_PERMISSIONS);
48+ return BatchGenerateResult {
49+ packages: vec![create_empty_package(); queries.len()
50+ ],
51+ error_code: ErrCode::PermissionDenied as i32,
52+ };
53+ }
54+
55+ if let Err(e) = validate_controlled_batch_params(&queries) {
56+ loge!("Invalid batch params: {:?}", e);
57+ return BatchGenerateResult {
58+ packages: vec![create_empty_package(); queries.len()],
59+ error_code: e.code as i32,
60+ };
61+ }
62+
63+ let mut packages = Vec::with_capacity(queries.len());
64+ let mut has_error = false;
65+
66+ for (idx, query) in queries.into_iter().enumerate() {
67+ match generate_single_controlled_package(query) {
68+ Ok(pkg) => packages.push(pkg),
69+ Err(e) => {
70+ loge!("Generate package failed at idx[{}], err={:?}",idx, e);
71+ packages.push(create_empty_package());
72+ has_error = true;
73+ }
74+ }
75+ }
76+
77+ BatchGenerateResult {
78+ packages,
79+ error_code: if has_error { ErrCode::GeneralError as i32 } else { ErrCode::Success as i32 },
80+ }
81+}
82+ 
83+fn check_controller_device_id_match(local_udid: &str, package: &RemoteAuthPackage) -> bool {
84+ local_udid == package.remote_message.device_info.controller_device_id
85+}
86+ 
87+/// Verifies controlled device packages.
88+pub fn verify_controlled_device_package(
89+ os_account_id: i32,
90+ packages: Vec<RemoteAuthPackage>
91+) -> BatchVerifyResult {
92+ let permission = CString::new(QUERY_TOOL_PERMISSIONS).unwrap();
93+ if unsafe { !CheckPermission(permission.as_ptr()) } {
94+ loge!("Permission denied! Need {}", QUERY_TOOL_PERMISSIONS);
95+ return BatchVerifyResult {
96+ results: Vec::new(),
97+ error_code: ErrCode::PermissionDenied as i32,
98+ };
99+ }
100+ 
101+ logi!("[verify_controlled_device_package] os_account_id={}, package_count={}", os_account_id, packages.len());
102+
103+ if packages.is_empty() || packages.len() > super::MAX_REMOTE_BATCH_COUNT {
104+ loge!("Invalid packages count: {}, max allowed: {}", packages.len(), super::MAX_REMOTE_BATCH_COUNT);
105+ return BatchVerifyResult {
106+ results: Vec::new(),
107+ error_code: ErrCode::InvalidArrayLen as i32,
108+ };
109+ }
110+
111+ for (_idx, package) in packages.iter().enumerate() {
112+ log_remote_auth_package(package);
113+ }
114+
115+ let local_udid = match crate::wrapper::get_device_udid(os_account_id) {
116+ Ok(udid) => udid,
117+ Err(e) => {
118+ loge!("Failed to get local udid: {:?}", e);
119+ return BatchVerifyResult {
120+ results: Vec::new(),
121+ error_code: e.code as i32,
122+ };
123+ }
124+ };
125+ 
126+ let current_time = match system_time_in_millis() {
127+ Ok(t) => t,
128+ Err(e) => {
129+ loge!("Failed to get system time: {:?}", e);
130+ return BatchVerifyResult {
131+ results: Vec::new(),
132+ error_code: e.code as i32,
133+ };
134+ }
135+ };
136+ 
137+ let mut results = Vec::with_capacity(packages.len());
138+ let mut system_error_code = ErrCode::Success as i32;
139+ 
140+ for package in packages.iter() {
141+ match validate_single_controlled_package(package, &local_udid, current_time, os_account_id) {
142+ Ok(result) => results.push(result),
143+ Err(e) => {
144+ loge!("System error during validation: {:?}", e);
145+ results.push(false);
146+ if system_error_code == ErrCode::Success as i32 {
147+ system_error_code = e.code as i32;
148+ }
149+ }
150+ }
151+ }
152+
153+ BatchVerifyResult {
154+ results,
155+ error_code: system_error_code,
156+ }
157+}
158+ 
159+fn validate_single_controlled_package(
160+ package: &RemoteAuthPackage,
161+ local_udid: &str,
162+ current_time: u64,
163+ os_account_id: i32,
164+) -> Result<bool> {
165+ if !check_controller_device_id_match(local_udid, package) {
166+ loge!("Local udid mismatch");
167+ return Ok(false);
168+ }
169+ 
170+ if let Err(e) = validate_remote_auth_message_fields(&package.remote_message.remote_auth_message) {
171+ loge!("Invalid remote_auth_message fields: {:?}", e);
172+ return Ok(false);
173+ }
174+ 
175+ // Verify cross-layer consistency: out challenge must match inner (signed) challenge
176+ if let Err(e) = super::validate_challenge_consistency(package) {
177+ loge!("Challenge consistency check failed: {:?}", e);
178+ return Ok(false);
179+ }
180+
181+ let timestamp = match parse_timestamp(&package.remote_message.remote_auth_message) {
182+ Ok(t) => t,
183+ Err(_) => {
184+ loge!("Failed to parse timestamp");
185+ return Ok(false);
186+ }
187+ };
188+ 
189+ let expire_time_ms = match parse_ticket_expire_time(&package.remote_message.remote_auth_message) {
190+ Ok(t) => t,
191+ Err(_) => {
192+ loge!("Failed to parse ticket expire time");
193+ return Ok(false);
194+ }
195+ };
196+ 
197+ if !validate_ticket_expiration(timestamp, expire_time_ms, current_time) {
198+ loge!("Ticket expired");
199+ return Ok(false);
200+ }
201+ 
202+ match verify_remote_auth_package(os_account_id, package) {
203+ Ok(result) => Ok(result),
204+ Err(e) => {
205+ if e.code == ErrCode::ArgEmpty {
206+ loge!("Business failure - missing field: {:?}", e);
207+ Ok(false)
208+ } else {
209+ loge!("System error during verification: {:?}", e);
210+ Err(e)
211+ }
212+ }
213+ }
214+}
215+ 
216+fn generate_single_controlled_package(query: PermissionQuery) -> Result<RemoteAuthPackage> {
217+ validate_controlled_permission_query(&query)?;
218+
219+ let (cli_infos, mut api_permissions) = super::parse_cli_and_permission(&query.operation_info)?;
220+
221+ if !cli_infos.is_empty() {
222+ super::batch_query_cli_permission(&cli_infos, &mut api_permissions)?;
223+ }
224+
225+ let challenge = generate_crypto_random_challenge()?;
226+ let timestamp = system_time_in_millis()?;
227+
228+ let remote_auth_message = build_remote_auth_message(&query, api_permissions, &challenge, timestamp)?;
229+
230+ let uid = Skeleton::calling_uid();
231+ let user_id = get_user_id(uid)?;
232+
233+ logi!("Generate package: user_id={}", user_id);
234+
235+ let sign_params = SignParams {
236+ os_account_id: user_id,
237+ uid: query.remote_info.domain_id.clone(),
238+ remote_auth_package: remote_auth_message,
239+ remote_control_token: query.remote_info.remote_control_params.remote_control_ticket.clone(),
240+ };
241+
242+ let sign_result = sign_remote_auth_package(sign_params)?;
243+
244+ cache_challenge(user_id, &challenge, timestamp, &sign_result.device_id_header)?;
245+ 
246+ let package = RemoteAuthPackage {
247+ remote_message: RemoteMessage {
248+ device_info: sign_result.device_id_header,
249+ remote_auth_message: sign_result.remote_auth_package,
250+ caller_bundle_name: String::new(),
251+ },
252+ challenge,
253+ ticket: sign_result.sign_info,
254+ };
255+
256+ log_remote_auth_package(&package);
257+
258+ Ok(package)
259+}
260+ 
261+fn validate_controlled_permission_query(query: &PermissionQuery) -> Result<()> {
262+ if query.remote_info.role != Role::Controlled {
263+ return macros_lib::log_throw_error!(ErrCode::DataTypeMismatch, "Invalid role: expected CONTROLLED");
264+ }
265+ if query.domain_id.is_empty() {
266+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "domain_id is empty");
267+ }
268+ if query.remote_info.remote_control_params.remote_control_ticket.is_empty() {
269+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "remote_control_ticket is empty");
270+ }
271+ if query.ticket_expire_time_ms <= 0 || query.ticket_expire_time_ms > super::MAX_REMOTE_TICKET_EXPIRE_TIME_MS {
272+ return macros_lib::log_throw_error!(ErrCode::InvalidArgument, "ticket_expire_time_ms out of range {}",
273+ query.ticket_expire_time_ms);
274+ }
275+ Ok(())
276+}
277+ 
278+fn build_remote_auth_message(
279+ query: &PermissionQuery,
280+ api_permissions: Vec<String>,
281+ challenge: &str,
282+ timestamp: u64,
283+) -> Result<String> {
284+ let mut builder = JsonBuilder::new();
285+ serialize_permission_query_to_message(&mut builder, query);
286+ builder.add_string("challenge", challenge);
287+ builder.add_u64("timestamp", timestamp);
288+ builder.add_string_array("permissions", api_permissions);
289+ builder.add_number("version", 1 as i64);
290+ 
291+ builder.build()
292+}
293+ 
294+fn validate_controlled_batch_params(queries: &[PermissionQuery]) -> Result<()> {
295+ if queries.is_empty() || queries.len() > super::MAX_REMOTE_BATCH_COUNT {
296+ return macros_lib::log_throw_error!(ErrCode::InvalidArrayLen,
297+ "Invalid queries count: {}, max allowed: {}",
298+ queries.len(), super::MAX_REMOTE_BATCH_COUNT);
299+ }
300+
301+ for (idx, query) in queries.iter().enumerate() {
302+ if query.operation_info.len() > super::MAX_REMOTE_PERMISSION_COUNT {
303+ return macros_lib::log_throw_error!(ErrCode::InvalidArrayLen,
304+ "Invalid operation_info count at idx[{}]: {}, max allowed: {}",
305+ idx, query.operation_info.len(), super::MAX_REMOTE_PERMISSION_COUNT);
306+ }
307+ }
308+
309+ Ok(())
310+}
311+ 
312+#[cfg(feature = "SAFTest")]
313+pub mod ut_controlled_device_stub {
314+ include! {"../../../../../test/secure_access_fence/unittest/ut_test/services/core_service/test_stub/remote_control/ut_controlled_device_stub.rs"}
315+}
@@ -0,0 +1,517 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! This module implements controller device package generation and verification.
17+ 
18+use ipc::Skeleton;
19+use saf_common::{get_user_id, JsonBuilder, new_object, object_add_string};
20+use saf_utils::system_time_in_millis;
21+use saf_definition::{macros_lib, ErrCode, Result,
22+ PermissionQuery, RemoteAuthPackage, Role, DeviceIdHeader, RemoteUserAuthItem,
23+ RemoteUserAuthResults, RemoteInfo, RemoteMessage, GrantType,
24+};
25+use crate::remote_control::{create_empty_package, log_remote_auth_package, parse_ticket_expire_time,
26+ parse_timestamp, serialize_permission_query_to_message, validate_remote_auth_message_fields, BatchGenerateResult, BatchVerifyResult};
27+use crate::remote_control::grant_record::{store_grant_record, StoreGrantRecordParams};
28+use crate::remote_control::remote_challenge_manager::verify_and_remove_challenge;
29+use crate::remote_control::account_based_auth_manager::{sign_remote_auth_package, verify_remote_auth_package, SignParams};
30+use crate::remote_control::grant_record::get_bundle_name_from_token;
31+use saf_log::{loge, logi};
32+use std::ffi::CString;
33+use std::os::raw::c_char as raw_c_char;
34+ 
35+extern "C" {
36+ fn CheckPermission(permission: *const raw_c_char) -> bool;
37+}
38+ 
39+const QUERY_TOOL_PERMISSIONS: &str = "ohos.permission.QUERY_TOOL_PERMISSIONS";
40+ 
41+const MAX_PERMISSION_LEN: usize = 128;
42+ 
43+/// Generates controller device packages for remote user auth results.
44+pub fn generate_controller_device_package(
45+ remote_user_auth_results: Vec<RemoteUserAuthResults>
46+) -> BatchGenerateResult {
47+ let permission = CString::new(QUERY_TOOL_PERMISSIONS).unwrap();
48+ if unsafe { !CheckPermission(permission.as_ptr()) } {
49+ loge!("Permission denied! Need {}", QUERY_TOOL_PERMISSIONS);
50+ return BatchGenerateResult {
51+ packages: vec![create_empty_package(); remote_user_auth_results.len().max(1)],
52+ error_code: ErrCode::PermissionDenied as i32,
53+ };
54+ }
55+
56+ if let Err(e) = validate_controller_batch_params(&remote_user_auth_results) {
57+ loge!("Invalid batch params: {:?}", e);
58+ return BatchGenerateResult {
59+ packages: vec![create_empty_package(); remote_user_auth_results.len()],
60+ error_code: e.code as i32,
61+ };
62+ }
63+
64+ let uid = Skeleton::calling_uid();
65+ let user_id = match get_user_id(uid) {
66+ Ok(id) => id,
67+ Err(e) => {
68+ loge!("Failed to get user_id: {:?}", e);
69+ return BatchGenerateResult {
70+ packages: vec![create_empty_package(); remote_user_auth_results.len()],
71+ error_code: ErrCode::InvalidOsAccountId as i32,
72+ };
73+ }
74+ };
75+
76+ let mut packages = Vec::with_capacity(remote_user_auth_results.len());
77+ let mut has_error = false;
78+
79+ for (idx, auth_result) in remote_user_auth_results.iter().enumerate() {
80+ match generate_single_controller_package(user_id, auth_result) {
81+ Ok(pkg) => {
82+ store_grant_record_if_success(user_id, &pkg, Role::Controller);
83+ packages.push(pkg);
84+ },
85+ Err(e) => {
86+ loge!("Generate controller package failed at idx[{}], err={:?}", idx, e);
87+ packages.push(create_empty_package());
88+ has_error = true;
89+ }
90+ }
91+ }
92+
93+ BatchGenerateResult {
94+ packages,
95+ error_code: if has_error { ErrCode::GeneralError as i32 } else { ErrCode::Success as i32 },
96+ }
97+}
98+ 
99+fn store_grant_record_if_success(user_id: i32, pkg: &RemoteAuthPackage, role: Role) {
100+ let caller_bundle_name = super::parse_caller_bundle_name_from_remote_auth_message(
101+ &pkg.remote_message.remote_auth_message
102+ ).unwrap_or_default();
103+ 
104+ if let Ok(params) = StoreGrantRecordParams::from_remote_auth_package(
105+ user_id,
106+ &pkg.remote_message.remote_auth_message,
107+ role,
108+ GrantType::RemoteGrant,
109+ caller_bundle_name
110+ ) {
111+ if params.permission_names.is_empty() {
112+ logi!("No GRANTED permissions found, skip storing grant record");
113+ return;
114+ }
115+ if let Err(e) = store_grant_record(params) {
116+ loge!("Failed to store grant record: {:?}", e);
117+ }
118+ }
119+}
120+ 
121+fn check_role_is_controller(role: Role) -> bool {
122+ role == Role::Controller
123+}
124+ 
125+/// Verifies controller device packages with remote info.
126+pub fn verify_controller_device_package(
127+ os_account_id: i32,
128+ packages: Vec<RemoteAuthPackage>,
129+ remote_info: &RemoteInfo
130+) -> BatchVerifyResult {
131+ let permission = CString::new(QUERY_TOOL_PERMISSIONS).unwrap();
132+ if unsafe { !CheckPermission(permission.as_ptr()) } {
133+ loge!("Permission denied! Need {}", QUERY_TOOL_PERMISSIONS);
134+ return BatchVerifyResult {
135+ results: Vec::new(),
136+ error_code: ErrCode::PermissionDenied as i32,
137+ };
138+ }
139+ 
140+ logi!("[verify_controller_device_package] os_account_id={}, package_count={}, domain_id={}",
141+ os_account_id, packages.len(), remote_info.domain_id);
142+
143+ if packages.is_empty() || packages.len() > super::MAX_REMOTE_BATCH_COUNT {
144+ loge!("Invalid packages count: {}, max allowed: {}", packages.len(), super::MAX_REMOTE_BATCH_COUNT);
145+ return BatchVerifyResult {
146+ results: Vec::new(),
147+ error_code: ErrCode::InvalidArrayLen as i32,
148+ };
149+ }
150+
151+ for (_idx, package) in packages.iter().enumerate() {
152+ log_remote_auth_package(package);
153+ }
154+
155+ if !check_role_is_controller(remote_info.role) {
156+ loge!("Invalid role for verification: expected CONTROLLER");
157+ return BatchVerifyResult {
158+ results: Vec::new(),
159+ error_code: ErrCode::DataTypeMismatch as i32,
160+ };
161+ }
162+
163+ let local_udid = match crate::wrapper::get_device_udid(os_account_id) {
164+ Ok(udid) => udid,
165+ Err(e) => {
166+ loge!("Failed to get local udid: {:?}", e);
167+ return BatchVerifyResult {
168+ results: Vec::new(),
169+ error_code: e.code as i32,
170+ };
171+ }
172+ };
173+ 
174+ let current_time = match system_time_in_millis() {
175+ Ok(t) => t,
176+ Err(e) => {
177+ loge!("Failed to get system time: {:?}", e);
178+ return BatchVerifyResult {
179+ results: Vec::new(),
180+ error_code: e.code as i32,
181+ };
182+ }
183+ };
184+ 
185+ let mut results = Vec::with_capacity(packages.len());
186+ let mut system_error_code = ErrCode::Success as i32;
187+ 
188+ for package in packages.iter() {
189+ match validate_and_verify_single_controller_package(
190+ package, &local_udid, current_time, os_account_id
191+ ) {
192+ Ok(result) => results.push(result),
193+ Err(e) => {
194+ loge!("System error during validation: {:?}", e);
195+ results.push(false);
196+ if system_error_code == ErrCode::Success as i32 {
197+ system_error_code = e.code as i32;
198+ }
199+ }
200+ }
201+ }
202+ 
203+ BatchVerifyResult {
204+ results,
205+ error_code: system_error_code,
206+ }
207+}
208+ 
209+fn validate_and_verify_single_controller_package(
210+ package: &RemoteAuthPackage,
211+ local_udid: &str,
212+ current_time: u64,
213+ os_account_id: i32,
214+) -> Result<bool> {
215+ let verify_result = match verify_single_controller_package(
216+ os_account_id, package, local_udid, current_time
217+ ) {
218+ Ok(result) => result,
219+ Err(e) => {
220+ if e.code == ErrCode::ArgEmpty || e.code == ErrCode::ReplayAttackDetected {
221+ loge!("Business failure: {:?}", e);
222+ return Ok(false);
223+ } else {
224+ loge!("System error during verification: {:?}", e);
225+ return Err(e);
226+ }
227+ }
228+ };
229+ 
230+ if verify_result {
231+ store_grant_record_if_success(os_account_id, package, Role::Controlled);
232+ }
233+ 
234+ Ok(verify_result)
235+}
236+ 
237+fn generate_single_controller_package(
238+ os_account_id: i32,
239+ auth_result: &RemoteUserAuthResults
240+) -> Result<RemoteAuthPackage> {
241+ let (local_udid, api_permissions, challenge, timestamp) =
242+ prepare_controller_package_data(os_account_id, auth_result)?;
243+ 
244+ let uid = Skeleton::calling_uid();
245+ let user_id = get_user_id(uid)?;
246+ 
247+ logi!("Generate controller package: user_id={}", user_id);
248+ 
249+ let package = build_and_sign_controller_package(
250+ &auth_result.permission_query,
251+ &auth_result.results,
252+ challenge,
253+ &local_udid,
254+ &api_permissions,
255+ user_id,
256+ timestamp,
257+ )?;
258+ 
259+ log_remote_auth_package(&package);
260+ 
261+ Ok(package)
262+}
263+ 
264+fn prepare_controller_package_data(
265+ os_account_id: i32,
266+ auth_result: &RemoteUserAuthResults
267+) -> Result<(String, Vec<String>, String, u64)> {
268+ validate_controller_permission_query(&auth_result.permission_query)?;
269+ validate_auth_results_permissions(&auth_result.results)?;
270+ 
271+ let local_udid = crate::wrapper::get_device_udid(os_account_id)?;
272+ 
273+ let (cli_infos, mut api_permissions) =
274+ super::parse_cli_and_permission(&auth_result.permission_query.operation_info)?;
275+ 
276+ if !cli_infos.is_empty() {
277+ super::batch_query_cli_permission(&cli_infos, &mut api_permissions)?;
278+ }
279+ 
280+ validate_permissions_match(&auth_result.results, &api_permissions)?;
281+ 
282+ let challenge = auth_result.permission_query.remote_info.remote_control_params.challenge.clone();
283+ if challenge.is_empty() {
284+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "challenge is empty");
285+ }
286+
287+ let timestamp = system_time_in_millis()?;
288+ 
289+ Ok((local_udid, api_permissions, challenge, timestamp))
290+}
291+ 
292+fn build_and_sign_controller_package(
293+ query: &PermissionQuery,
294+ auth_results: &[RemoteUserAuthItem],
295+ challenge: String,
296+ local_udid: &str,
297+ api_permissions: &[String],
298+ user_id: i32,
299+ timestamp: u64,
300+) -> Result<RemoteAuthPackage> {
301+ let caller_bundle_name = get_bundle_name_from_token(query.caller_token_id)
302+ .unwrap_or_default();
303+
304+ let remote_auth_message = build_controller_remote_auth_message(
305+ query, auth_results, &challenge, timestamp, local_udid, api_permissions, &caller_bundle_name
306+ )?;
307+ 
308+ let sign_params = SignParams {
309+ os_account_id: user_id,
310+ uid: query.remote_info.domain_id.clone(),
311+ remote_auth_package: remote_auth_message,
312+ remote_control_token: String::new(),
313+ };
314+ 
315+ let sign_result = sign_remote_auth_package(sign_params)?;
316+ 
317+ Ok(RemoteAuthPackage {
318+ remote_message: RemoteMessage {
319+ device_info: sign_result.device_id_header,
320+ remote_auth_message: sign_result.remote_auth_package,
321+ caller_bundle_name,
322+ },
323+ challenge,
324+ ticket: sign_result.sign_info,
325+ })
326+}
327+ 
328+fn verify_single_controller_package(
329+ os_account_id: i32,
330+ package: &RemoteAuthPackage,
331+ local_udid: &str,
332+ current_time: u64
333+) -> Result<bool> {
334+ // Validate cross-layer consistency first
335+ let _inner_challenge = match super::validate_challenge_consistency(package) {
336+ Ok(c) => c,
337+ Err(e) => {
338+ loge!("Challenge consistency check failed: {:?}", e);
339+ return Ok(false);
340+ }
341+ };
342+ 
343+ if let Err(e) = validate_remote_auth_message_fields(&package.remote_message.remote_auth_message) {
344+ loge!("Invalid remote_auth_message fields: {:?}", e);
345+ return Ok(false);
346+ }
347+ 
348+ let timestamp = match parse_timestamp(&package.remote_message.remote_auth_message) {
349+ Ok(t) => t,
350+ Err(_) => {
351+ loge!("Failed to parse timestamp");
352+ return Ok(false);
353+ }
354+ };
355+ 
356+ let expire_time_ms = match parse_ticket_expire_time(&package.remote_message.remote_auth_message) {
357+ Ok(t) => t,
358+ Err(_) => {
359+ loge!("Failed to parse ticket expire time");
360+ return Ok(false);
361+ }
362+ };
363+ 
364+ if !crate::remote_control::validate_ticket_expiration(timestamp, expire_time_ms, current_time) {
365+ loge!("Ticket expired");
366+ return Ok(false);
367+ }
368+
369+ let verify_result = verify_remote_auth_package(os_account_id, package)?;
370+ if !verify_result {
371+ return Ok(false);
372+ }
373+ 
374+ let controller_device_id = match super::parse_local_device_id_from_remote_auth_message(
375+ &package.remote_message.remote_auth_message
376+ ) {
377+ Ok(id) => id,
378+ Err(e) => {
379+ loge!("Failed to parse localDeviceId from remote_auth_message: {:?}", e);
380+ return Ok(false);
381+ }
382+ };
383+
384+ let device_id_header = DeviceIdHeader {
385+ controlled_device_id: local_udid.to_string(),
386+ controller_device_id,
387+ };
388+
389+ verify_and_remove_challenge(os_account_id, &package.challenge, &device_id_header)?;
390+
391+ Ok(true)
392+}
393+ 
394+fn validate_controller_permission_query(query: &PermissionQuery) -> Result<()> {
395+ if query.remote_info.role != Role::Controller {
396+ return macros_lib::log_throw_error!(ErrCode::DataTypeMismatch,
397+ "Invalid role: expected CONTROLLER");
398+ }
399+ 
400+ if query.domain_id.is_empty() {
401+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "domain_id is empty");
402+ }
403+ 
404+ if query.remote_info.remote_control_params.challenge.is_empty() {
405+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "challenge is empty");
406+ }
407+ 
408+ if query.operation_info.is_empty() {
409+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "operation_info is empty");
410+ }
411+ if query.ticket_expire_time_ms <= 0 || query.ticket_expire_time_ms > super::MAX_REMOTE_TICKET_EXPIRE_TIME_MS {
412+ return macros_lib::log_throw_error!(ErrCode::InvalidArgument, "ticket_expire_time_ms out of range {}",
413+ query.ticket_expire_time_ms);
414+ }
415+ Ok(())
416+}
417+ 
418+fn validate_auth_results_permissions(results: &[RemoteUserAuthItem]) -> Result<()> {
419+ if results.is_empty() {
420+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "authResults is empty");
421+ }
422+
423+ for (idx, item) in results.iter().enumerate() {
424+ if item.permission.is_empty() {
425+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty,
426+ "Permission at idx[{}] is empty", idx);
427+ }
428+ if item.permission.len() >= MAX_PERMISSION_LEN {
429+ return macros_lib::log_throw_error!(ErrCode::InvalidArgSize,
430+ "Permission at idx[{}] exceeds max length {}, actual len={}",
431+ idx, MAX_PERMISSION_LEN, item.permission.len());
432+ }
433+ }
434+ Ok(())
435+}
436+ 
437+fn validate_permissions_match(results: &[RemoteUserAuthItem], api_permissions: &[String]) -> Result<()> {
438+ let results_permissions: std::collections::HashSet<&str> = results
439+ .iter()
440+ .map(|r| r.permission.as_str())
441+ .collect();
442+
443+ let api_permissions_set: std::collections::HashSet<&str> = api_permissions
444+ .iter()
445+ .map(|p| p.as_str())
446+ .collect();
447+
448+ if results_permissions != api_permissions_set {
449+ loge!("Permissions mismatch: results_count={}, api_permissions_count={}",
450+ results_permissions.len(), api_permissions_set.len());
451+ return macros_lib::log_throw_error!(ErrCode::DataTypeMismatch,
452+ "Permissions in results do not match permissions from operationInfo");
453+ }
454+
455+ Ok(())
456+}
457+ 
458+fn build_controller_remote_auth_message(
459+ query: &PermissionQuery,
460+ auth_results: &[RemoteUserAuthItem],
461+ challenge: &str,
462+ timestamp: u64,
463+ local_device_id: &str,
464+ permissions: &[String],
465+ caller_bundle_name: &str
466+) -> Result<String> {
467+ let mut builder = JsonBuilder::new();
468+ serialize_permission_query_to_message(&mut builder, query);
469+ 
470+ builder.add_string("challenge", challenge);
471+ builder.add_u64("timestamp", timestamp);
472+
473+ let mut auth_results_array = Vec::with_capacity(auth_results.len());
474+ for item in auth_results {
475+ let mut item_obj = new_object();
476+ object_add_string(&mut item_obj, "permission", &item.permission);
477+ object_add_string(&mut item_obj, "authResult", &item.auth_result);
478+ auth_results_array.push(item_obj);
479+ }
480+ builder.add_object_array("authResults", auth_results_array);
481+ 
482+ builder.add_string_array("permissions", permissions.to_vec());
483+ builder.add_string("localDeviceId", local_device_id);
484+ builder.add_string("callerBundleName", caller_bundle_name);
485+ builder.add_number("version", 1 as i64);
486+ 
487+ builder.build()
488+}
489+ 
490+fn validate_controller_batch_params(auth_results: &[RemoteUserAuthResults]) -> Result<()> {
491+ if auth_results.is_empty() || auth_results.len() > super::MAX_REMOTE_BATCH_COUNT {
492+ return macros_lib::log_throw_error!(ErrCode::InvalidArrayLen,
493+ "Invalid auth_results count: {}, max allowed: {}",
494+ auth_results.len(), super::MAX_REMOTE_BATCH_COUNT);
495+ }
496+
497+ for (idx, auth_result) in auth_results.iter().enumerate() {
498+ if auth_result.permission_query.operation_info.len() > super::MAX_REMOTE_PERMISSION_COUNT {
499+ return macros_lib::log_throw_error!(ErrCode::InvalidArrayLen,
500+ "Invalid operation_info count at idx[{}]: {}, max allowed: {}",
501+ idx, auth_result.permission_query.operation_info.len(), super::MAX_REMOTE_PERMISSION_COUNT);
502+ }
503+
504+ if auth_result.results.len() > super::MAX_REMOTE_PERMISSION_COUNT {
505+ return macros_lib::log_throw_error!(ErrCode::InvalidArrayLen,
506+ "Invalid results count at idx[{}]: {}, max allowed: {}",
507+ idx, auth_result.results.len(), super::MAX_REMOTE_PERMISSION_COUNT);
508+ }
509+ }
510+
511+ Ok(())
512+}
513+ 
514+#[cfg(feature = "SAFTest")]
515+pub mod ut_controller_device_stub {
516+ include! {"../../../../../test/secure_access_fence/unittest/ut_test/services/core_service/test_stub/remote_control/ut_controller_device_stub.rs"}
517+}
@@ -0,0 +1,238 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! This module implements grant record storage functionality.
17+ 
18+use ylong_json::JsonValue;
19+use saf_definition::{macros_lib, ErrCode, GrantType, Result, Role};
20+use saf_log::{loge, logi};
21+use saf_utils::{get_compact_json_value, system_time_in_millis};
22+ 
23+#[cfg(not(feature = "SAFTest"))]
24+use std::collections::HashMap;
25+#[cfg(not(feature = "SAFTest"))]
26+use saf_plugin::saf_plugin::SAFPlugin;
27+#[cfg(not(feature = "SAFTest"))]
28+use saf_plugin_interface::plugin_interface::{
29+ EventType, ExtMap, STORE_GRANT_RECORD_KEYS,
30+};
31+#[cfg(not(feature = "SAFTest"))]
32+use saf_definition::Value;
33+ 
34+const MAX_BUNDLE_NAME_LEN: usize = 256;
35+ 
36+#[cfg(not(feature = "SAFTest"))]
37+extern "C" {
38+ fn GetBundleNameFromTokenId(
39+ tokenId: i32,
40+ bundleName: *mut std::ffi::c_char,
41+ len: *mut i32
42+ ) -> i32;
43+}
44+ 
45+#[cfg(not(feature = "SAFTest"))]
46+pub fn get_bundle_name_from_token(caller_token_id: i32) -> Result<String> {
47+ let mut bundle_name_buf = vec![0u8; MAX_BUNDLE_NAME_LEN];
48+ let mut len = MAX_BUNDLE_NAME_LEN as i32;
49+ 
50+ let ret = unsafe {
51+ GetBundleNameFromTokenId(
52+ caller_token_id,
53+ bundle_name_buf.as_mut_ptr() as *mut std::ffi::c_char,
54+ &mut len,
55+ )
56+ };
57+ 
58+ if ret != 0 {
59+ loge!("GetBundleNameFromTokenId failed, ret={}", ret);
60+ return Err(macros_lib::log_and_into_saf_error!(ErrCode::GeneralError,
61+ "GetBundleNameFromTokenId failed"));
62+ }
63+ 
64+ unsafe {
65+ Ok(std::ffi::CStr::from_ptr(bundle_name_buf.as_ptr() as *const std::ffi::c_char)
66+ .to_str()
67+ .map_err(|e| {
68+ loge!("Invalid UTF-8 in bundle name: {}", e);
69+ macros_lib::log_and_into_saf_error!(ErrCode::GeneralError,
70+ "Invalid UTF-8 in bundle name")
71+ })?
72+ .to_string())
73+ }
74+}
75+ 
76+/// Store grant record params structure.
77+#[derive(Debug, Clone)]
78+pub struct StoreGrantRecordParams {
79+ /// OS account ID.
80+ pub os_account_id: i32,
81+ /// Controlled device name.
82+ pub controlled_device_name: String,
83+ /// Controller device name.
84+ pub controller_device_name: String,
85+ /// Is self grant flag.
86+ pub is_self_grant: bool,
87+ /// Permission names.
88+ pub permission_names: Vec<String>,
89+ /// Device role.
90+ pub device_role: Role,
91+ /// Calling bundle name.
92+ pub calling_bundle_name: String,
93+ /// Grant type.
94+ pub grant_type: GrantType,
95+ /// Timestamp.
96+ pub timestamp: u64,
97+}
98+ 
99+impl StoreGrantRecordParams {
100+/// Creates a new StoreGrantRecordParams from remote auth package.
101+ pub fn from_remote_auth_package(
102+ os_account_id: i32,
103+ remote_auth_message: &str,
104+ device_role: Role,
105+ grant_type: GrantType,
106+ calling_bundle_name: String,
107+ ) -> Result<Self> {
108+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
109+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
110+ "parse remote_auth_message failed: {}", e)
111+ })?;
112+ 
113+ let (controlled_device_name, controller_device_name) = Self::parse_device_names(&json)?;
114+ let permission_names = Self::parse_permission_names(remote_auth_message)?;
115+ let timestamp = system_time_in_millis()?;
116+ 
117+ Ok(Self {
118+ os_account_id,
119+ controlled_device_name,
120+ controller_device_name,
121+ is_self_grant: false,
122+ permission_names,
123+ device_role,
124+ calling_bundle_name,
125+ grant_type,
126+ timestamp,
127+ })
128+ }
129+ 
130+ fn parse_device_names(json: &JsonValue) -> Result<(String, String)> {
131+ let controlled_device_name = get_compact_json_value(json, "controlledDeviceName")
132+ .map_err(|e| {
133+ loge!("Failed to get controlledDeviceName: {:?}", e);
134+ macros_lib::log_and_into_saf_error!(ErrCode::ArgEmpty,
135+ "controlledDeviceName not found in remote_auth_message")
136+ })?;
137+
138+ let controller_device_name = get_compact_json_value(json, "controllerDeviceName")
139+ .map_err(|e| {
140+ loge!("Failed to get controllerDeviceName: {:?}", e);
141+ macros_lib::log_and_into_saf_error!(ErrCode::ArgEmpty,
142+ "controllerDeviceName not found in remote_auth_message")
143+ })?;
144+
145+ Ok((controlled_device_name, controller_device_name))
146+ }
147+
148+ fn parse_permission_names(remote_auth_message: &str) -> Result<Vec<String>> {
149+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
150+ loge!("Failed to parse JSON: {:?}", e);
151+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
152+ "parse remote_auth_message failed: {}", e)
153+ })?;
154+
155+ match &json["authResults"] {
156+ JsonValue::Null => {
157+ loge!("authResults not found or is null");
158+ macros_lib::log_throw_error!(ErrCode::ArgEmpty,
159+ "authResults not found in remote_auth_message")
160+ }
161+ JsonValue::Array(arr) => {
162+ let permissions: Vec<String> = arr.iter()
163+ .filter_map(|item| {
164+ match (&item["permission"], &item["authResult"]) {
165+ (JsonValue::String(perm), JsonValue::String(result))
166+ if result.to_uppercase() == "GRANTED" => Some(perm.clone()),
167+ _ => None,
168+ }
169+ })
170+ .collect();
171+ Ok(permissions)
172+ }
173+ _ => {
174+ loge!("authResults is not an array");
175+ macros_lib::log_throw_error!(ErrCode::DataTypeMismatch,
176+ "authResults is not an array")
177+ }
178+ }
179+ }
180+}
181+ 
182+#[cfg(not(feature = "SAFTest"))]
183+pub fn store_grant_record(params: StoreGrantRecordParams) -> Result<()> {
184+ logi!("[store_grant_record] os_account_id={}, device_role={:?}, grant_type={:?}",
185+ params.os_account_id, params.device_role, params.grant_type);
186+
187+ let loader = SAFPlugin::get_instance().load_plugin().map_err(|e| {
188+ macros_lib::log_and_into_saf_error!(ErrCode::PluginNotSupport, "load plugin failed: {}", e)
189+ })?;
190+
191+ let mut ext_map: ExtMap = HashMap::new();
192+ let os_account_id_u32 = u32::try_from(params.os_account_id).map_err(|_| {
193+ macros_lib::log_and_into_saf_error!(ErrCode::InvalidOsAccountId,
194+ "os_account_id is negative: {}", params.os_account_id)
195+ })?;
196+
197+ ext_map.insert(STORE_GRANT_RECORD_KEYS.os_account_id, Value::Number(os_account_id_u32));
198+ ext_map.insert(STORE_GRANT_RECORD_KEYS.controlled_device_name, Value::String(params.controlled_device_name));
199+ ext_map.insert(STORE_GRANT_RECORD_KEYS.controller_device_name, Value::String(params.controller_device_name));
200+ ext_map.insert(STORE_GRANT_RECORD_KEYS.is_self_grant, Value::Bool(params.is_self_grant));
201+ ext_map.insert(STORE_GRANT_RECORD_KEYS.permission_names, Value::StringList(params.permission_names));
202+ ext_map.insert(STORE_GRANT_RECORD_KEYS.device_role, Value::Number(params.device_role as u32));
203+ ext_map.insert(STORE_GRANT_RECORD_KEYS.calling_bundle_name, Value::String(params.calling_bundle_name));
204+ ext_map.insert(STORE_GRANT_RECORD_KEYS.grant_type, Value::Number(params.grant_type as u32));
205+ ext_map.insert(STORE_GRANT_RECORD_KEYS.timestamp, Value::String(params.timestamp.to_string()));
206+
207+ let _ = loader.process_event(EventType::StoreGrantRecord, &mut ext_map).map_err(|e| {
208+ macros_lib::log_and_into_saf_error!(
209+ ErrCode::try_from(e).unwrap_or(ErrCode::GeneralError),
210+ "store_grant_record process_event failed")
211+ })?;
212+
213+ logi!("[store_grant_record] success");
214+ Ok(())
215+}
216+ 
217+// ======================== SAFTest mock implementations ========================
218+ 
219+#[cfg(feature = "SAFTest")]
220+use std::sync::atomic::{AtomicBool, Ordering};
221+#[cfg(feature = "SAFTest")]
222+use lazy_static::lazy_static;
223+ 
224+#[cfg(feature = "SAFTest")]
225+lazy_static! {
226+ static ref MOCK_BUNDLE_NAME: String = "mock_bundle_name".to_string();
227+}
228+ 
229+#[cfg(feature = "SAFTest")]
230+fn get_bundle_name_from_token(_caller_token_id: i32) -> Result<String> {
231+ Ok(MOCK_BUNDLE_NAME.clone())
232+}
233+ 
234+#[cfg(feature = "SAFTest")]
235+pub fn store_grant_record(_params: StoreGrantRecordParams) -> Result<()> {
236+ logi!("[store_grant_record] mock implementation for test");
237+ Ok(())
238+}
@@ -0,0 +1,305 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! This module implements remote control functionality for GenerateControlledDevicePackage.
17+ 
18+mod cli_manager;
19+mod grant_record;
20+mod remote_challenge_manager;
21+mod account_based_auth_manager;
22+ 
23+#[cfg(not(feature = "SAFTest"))]
24+mod controlled_device;
25+ 
26+#[cfg(not(feature = "SAFTest"))]
27+mod controller_device;
28+ 
29+use saf_common::{JsonBuilder, new_object, object_add_number, object_add_string};
30+use saf_definition::{macros_lib, ErrCode, Result, RemoteAuthPackage, RemoteMessage, DeviceIdHeader, OperationType, PermissionQuery};
31+use saf_log::logi;
32+use saf_utils::{JsonValue, get_compact_json_value};
33+ 
34+/// Batch generate result for device packages.
35+pub struct BatchGenerateResult {
36+ /// Generated packages (same count as input queries, failures use empty objects).
37+ pub packages: Vec<RemoteAuthPackage>,
38+ /// Error code (0 for full success, non-zero for partial or full failure).
39+ pub error_code: i32,
40+}
41+ 
42+/// Batch verify result for device packages.
43+pub struct BatchVerifyResult {
44+ /// Verification results (same count as input packages).
45+ pub results: Vec<bool>,
46+ /// System error code (0 for no system error, non-zero for system error encountered).
47+ pub error_code: i32,
48+}
49+ 
50+const MAX_REMOTE_BATCH_COUNT: usize = 10;
51+const MAX_REMOTE_PERMISSION_COUNT: usize = 40;
52+ 
53+fn log_remote_auth_package(package: &RemoteAuthPackage) {
54+ let remote_message = &package.remote_message;
55+ let device_info = &remote_message.device_info;
56+ logi!(
57+ "RemoteAuthPackage: ticket_len={}, \
58+ controller_device_id_len={}, controlled_device_id_len={}, \
59+ remote_auth_message_len={}",
60+ package.ticket.len(),
61+ device_info.controller_device_id.len(),
62+ device_info.controlled_device_id.len(),
63+ remote_message.remote_auth_message.len()
64+ );
65+}
66+ 
67+fn create_empty_package() -> RemoteAuthPackage {
68+ RemoteAuthPackage {
69+ remote_message: RemoteMessage {
70+ device_info: DeviceIdHeader {
71+ controller_device_id: String::new(),
72+ controlled_device_id: String::new(),
73+ },
74+ remote_auth_message: String::new(),
75+ caller_bundle_name: String::new(),
76+ },
77+ challenge: String::new(),
78+ ticket: String::new(),
79+ }
80+}
81+ 
82+fn parse_ticket_expire_time(remote_auth_message: &str) -> Result<i32> {
83+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
84+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
85+ "parse remote_auth_message failed: {}", e)
86+ })?;
87+ 
88+ let expire_time_str = get_compact_json_value(&json, "ticketExpireTimeMs")
89+ .map(|s| s.trim_matches('"').to_string())
90+ .unwrap_or_default();
91+ 
92+ expire_time_str.parse::<i32>().map_err(|e| {
93+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
94+ "parse ticketExpireTimeMs failed: {}", e)
95+ })
96+}
97+ 
98+fn parse_challenge_from_remote_auth_message(remote_auth_message: &str) -> Result<String> {
99+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
100+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
101+ "parse remote_auth_message failed: {}", e)
102+ })?;
103+ 
104+ let challenge = get_compact_json_value(&json, "challenge")
105+ .map(|s| s.trim_matches('"').to_string())
106+ .unwrap_or_default();
107+ 
108+ if challenge.is_empty() {
109+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "challenge is empty");
110+ }
111+ 
112+ Ok(challenge)
113+}
114+ 
115+fn validate_challenge_consistency(package: &RemoteAuthPackage) -> Result<String> {
116+ let inner_challenge = parse_challenge_from_remote_auth_message(&package.remote_message.remote_auth_message)?;
117+ 
118+ if inner_challenge != package.challenge {
119+ return macros_lib::log_throw_error!(ErrCode::InvalidArgument, "challenge mismatch!");
120+ }
121+ Ok(inner_challenge)
122+}
123+ 
124+fn parse_caller_bundle_name_from_remote_auth_message(remote_auth_message: &str) -> Result<String> {
125+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
126+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
127+ "parse remote_auth_message failed: {}", e)
128+ })?;
129+ 
130+ let bundle_name = get_compact_json_value(&json, "callerBundleName")
131+ .map(|s| s.trim_matches('"').to_string())
132+ .unwrap_or_default();
133+ 
134+ Ok(bundle_name)
135+}
136+ 
137+fn parse_local_device_id_from_remote_auth_message(remote_auth_message: &str) -> Result<String> {
138+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
139+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
140+ "parse remote_auth_message failed: {}", e)
141+ })?;
142+ 
143+ let local_device_id = get_compact_json_value(&json, "localDeviceId")
144+ .map(|s| s.trim_matches('"').to_string())
145+ .unwrap_or_default();
146+ 
147+ Ok(local_device_id)
148+}
149+ 
150+const ALLOWED_REMOTE_AUTH_MESSAGE_FIELDS: &[&str] = &[
151+ "operationInfo",
152+ "role",
153+ "ticketExpireTimeMs",
154+ "domainId",
155+ "controlledDeviceName",
156+ "controllerDeviceName",
157+ "signVerifyMsg",
158+ "callerTokenId",
159+ "challenge",
160+ "timestamp",
161+ "permissions",
162+ "authResults",
163+ "localDeviceId",
164+ "callerBundleName",
165+ "version"
166+];
167+ 
168+fn validate_remote_auth_message_fields(remote_auth_message: &str) -> Result<()> {
169+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
170+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
171+ "parse remote_auth_message failed: {}", e)
172+ })?;
173+ 
174+ let obj = match json {
175+ JsonValue::Object(ref obj) => obj,
176+ _ => return macros_lib::log_throw_error!(ErrCode::DataTypeMismatch,
177+ "remote_auth_message is not a JSON object"),
178+ };
179+ 
180+ let allowed_set: std::collections::HashSet<&str> = ALLOWED_REMOTE_AUTH_MESSAGE_FIELDS.iter().copied().collect();
181+
182+ for (key, _) in obj.iter() {
183+ if !allowed_set.contains(key.as_str()) {
184+ return macros_lib::log_throw_error!(ErrCode::InvalidArgument,
185+ "Unexpected field '{}' in remote_auth_message - potential MITM attack detected", key);
186+ }
187+ }
188+ 
189+ Ok(())
190+}
191+ 
192+fn parse_timestamp(remote_auth_message: &str) -> Result<u64> {
193+ let json = JsonValue::from_text(remote_auth_message).map_err(|e| {
194+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
195+ "parse remote_auth_message failed: {}", e)
196+ })?;
197+ 
198+ let ts_str = get_compact_json_value(&json, "timestamp")
199+ .map(|s| s.trim_matches('"').to_string())
200+ .unwrap_or_default();
201+ 
202+ ts_str.parse::<u64>().map_err(|e| {
203+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
204+ "parse timestamp failed: {}", e)
205+ })
206+}
207+ 
208+/// Maximum ticket expire time for remote control scenario: 1 day
209+pub const MAX_REMOTE_TICKET_EXPIRE_TIME_MS: i32 = 24 * 60 * 60 * 1000;
210+fn validate_ticket_expiration(timestamp: u64, expire_time_ms: i32, current_time: u64) -> bool {
211+ if expire_time_ms <= 0 || expire_time_ms > MAX_REMOTE_TICKET_EXPIRE_TIME_MS {
212+ return false;
213+ }
214+ match timestamp.checked_add(expire_time_ms as u64) {
215+ Some(expire_time) => expire_time >= current_time,
216+ None => false,
217+ }
218+}
219+ 
220+const REMOTE_CHALLENGE_SIZE: u32 = 16;
221+ 
222+fn generate_crypto_random_challenge() -> Result<String> {
223+ let mut buf = vec![0u8; REMOTE_CHALLENGE_SIZE as usize];
224+ crate::ticket_operation::generate_random_bytes(&mut buf, REMOTE_CHALLENGE_SIZE)?;
225+ let encoded = crate::ticket_operation::base64_encode(&buf)?;
226+ Ok(String::from_utf8_lossy(&encoded).to_string())
227+}
228+fn parse_cli_and_permission(
229+ operation_info: &[saf_definition::OperationInfo]
230+) -> Result<(Vec<saf_definition::CommandInfo>, Vec<String>)> {
231+ let mut cli_infos = Vec::with_capacity(operation_info.len());
232+ let mut api_permissions = Vec::with_capacity(operation_info.len());
233+
234+ for op in operation_info {
235+ match op.operation_type {
236+ saf_definition::OperationType::Cli => {
237+ if op.cli_cmd_info.cmd_name.is_empty() {
238+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "CLI command name is empty");
239+ }
240+ cli_infos.push(op.cli_cmd_info.clone());
241+ },
242+ saf_definition::OperationType::Api => {
243+ if op.permission.is_empty() {
244+ return macros_lib::log_throw_error!(ErrCode::ArgEmpty, "API permission is empty");
245+ }
246+ api_permissions.push(op.permission.clone());
247+ },
248+ }
249+ }
250+ Ok((cli_infos, api_permissions))
251+}
252+ 
253+fn serialize_permission_query_to_message(builder: &mut JsonBuilder, query: &PermissionQuery) {
254+ let mut op_array = Vec::with_capacity(query.operation_info.len());
255+ for op in &query.operation_info {
256+ let mut op_obj = new_object();
257+ object_add_number(&mut op_obj, "operationType", op.operation_type as i64);
258+ if op.operation_type == OperationType::Cli {
259+ let mut cli_obj = new_object();
260+ object_add_string(&mut cli_obj, "cliCmdName", &op.cli_cmd_info.cmd_name);
261+ object_add_string(&mut cli_obj, "subCliCmdName", &op.cli_cmd_info.sub_cmd);
262+ op_obj.insert("info".to_string(), JsonValue::Object(cli_obj));
263+ } else {
264+ op_obj.insert("info".to_string(), JsonValue::String(op.permission.clone()));
265+ }
266+ op_array.push(op_obj);
267+ }
268+ builder.add_object_array("operationInfo", op_array);
269+
270+ builder.add_number("role", query.remote_info.role as i64);
271+ builder.add_number("ticketExpireTimeMs", query.ticket_expire_time_ms as i64);
272+ builder.add_string("domainId", &query.remote_info.domain_id);
273+ builder.add_string("controlledDeviceName", &query.remote_info.remote_control_params.controlled_device_name);
274+ builder.add_string("controllerDeviceName", &query.remote_info.remote_control_params.controller_device_name);
275+ builder.add_string("signVerifyMsg", &query.remote_info.remote_control_params.sign_verify_msg);
276+ builder.add_number("callerTokenId", query.caller_token_id as i64);
277+}
278+ 
279+pub use controlled_device::{
280+ generate_controlled_device_package,
281+ verify_controlled_device_package,
282+};
283+ 
284+pub use controller_device::{
285+ generate_controller_device_package,
286+ verify_controller_device_package,
287+};
288+ 
289+pub use grant_record::{store_grant_record, StoreGrantRecordParams};
290+ 
291+pub use cli_manager::batch_query_cli_permission;
292+ 
293+// ======================== SAFTest module declarations ========================
294+ 
295+#[cfg(feature = "SAFTest")]
296+pub mod controlled_device;
297+ 
298+#[cfg(feature = "SAFTest")]
299+pub mod controller_device;
300+ 
301+#[cfg(feature = "SAFTest")]
302+pub use controlled_device::ut_controlled_device_stub;
303+ 
304+#[cfg(feature = "SAFTest")]
305+pub use controller_device::ut_controller_device_stub;
@@ -0,0 +1,329 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Challenge cache for remote control verification.
17+ 
18+#[cfg(not(feature = "SAFTest"))]
19+use std::fs::{self, File, OpenOptions};
20+#[cfg(not(feature = "SAFTest"))]
21+use std::io::{BufRead, BufReader, Write};
22+#[cfg(not(feature = "SAFTest"))]
23+use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
24+#[cfg(not(feature = "SAFTest"))]
25+use std::path::Path;
26+#[cfg(not(feature = "SAFTest"))]
27+use std::sync::Mutex;
28+ 
29+use saf_definition::{macros_lib, ErrCode, Result, DeviceIdHeader};
30+#[cfg(not(feature = "SAFTest"))]
31+use saf_log::{loge, logi};
32+#[cfg(not(feature = "SAFTest"))]
33+use saf_utils::{JsonValue, get_compact_json_value};
34+#[cfg(not(feature = "SAFTest"))]
35+use saf_common::JsonBuilder;
36+ 
37+#[cfg(not(feature = "SAFTest"))]
38+static CHALLENGE_CACHE_LOCK: Mutex<()> = Mutex::new(());
39+ 
40+#[cfg(not(feature = "SAFTest"))]
41+const CACHE_DIR_PREFIX: &str = "/data/service/el2";
42+#[cfg(not(feature = "SAFTest"))]
43+const CACHE_DIR_SUFFIX: &str = "secure_access_fence/agent_plugin";
44+#[cfg(not(feature = "SAFTest"))]
45+const CACHE_FILE_NAME: &str = "challenge_cache_list.txt";
46+#[cfg(not(feature = "SAFTest"))]
47+const FILE_MODE: u32 = 0o640;
48+ 
49+#[cfg(not(feature = "SAFTest"))]
50+#[derive(Debug, Clone)]
51+struct ChallengeCacheEntry {
52+ timestamp: u64,
53+ controller_device_id: String,
54+ controlled_device_id: String,
55+}
56+ 
57+#[cfg(not(feature = "SAFTest"))]
58+fn serialize_cache_entry(entry: &ChallengeCacheEntry) -> Result<String> {
59+ let mut builder = JsonBuilder::new();
60+ builder.add_u64("timestamp", entry.timestamp);
61+ builder.add_string("controllerDeviceId", &entry.controller_device_id);
62+ builder.add_string("controlledDeviceId", &entry.controlled_device_id);
63+ builder.build()
64+}
65+ 
66+#[cfg(not(feature = "SAFTest"))]
67+fn deserialize_cache_entry(json: &str) -> Result<ChallengeCacheEntry> {
68+ let parsed = JsonValue::from_text(json).map_err(|e| {
69+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
70+ "Failed to parse cache entry JSON: {}", e)
71+ })?;
72+ let timestamp_str = get_compact_json_value(&parsed, "timestamp")
73+ .map(|s| s.trim_matches('"').to_string())
74+ .unwrap_or_default();
75+ let timestamp = timestamp_str.parse::<u64>().map_err(|e| {
76+ macros_lib::log_and_into_saf_error!(ErrCode::JsonParseError,
77+ "Failed to parse timestamp: {}", e)
78+ })?;
79+ let controller_device_id = get_compact_json_value(&parsed, "controllerDeviceId")
80+ .map(|s| s.trim_matches('"').to_string())
81+ .unwrap_or_default();
82+ let controlled_device_id = get_compact_json_value(&parsed, "controlledDeviceId")
83+ .map(|s| s.trim_matches('"').to_string())
84+ .unwrap_or_default();
85+ Ok(ChallengeCacheEntry { timestamp, controller_device_id, controlled_device_id })
86+}
87+ 
88+#[cfg(not(feature = "SAFTest"))]
89+fn get_cache_file_path(os_account_id: i32) -> String {
90+ format!("{}/{}/{}", CACHE_DIR_PREFIX, os_account_id, CACHE_DIR_SUFFIX)
91+}
92+ 
93+#[cfg(not(feature = "SAFTest"))]
94+fn get_cache_file_full_path(os_account_id: i32) -> String {
95+ format!("{}/{}", get_cache_file_path(os_account_id), CACHE_FILE_NAME)
96+}
97+ 
98+#[cfg(not(feature = "SAFTest"))]
99+fn ensure_cache_dir_exists(os_account_id: i32) -> Result<()> {
100+ let dir_path = get_cache_file_path(os_account_id);
101+ let path = Path::new(&dir_path);
102+
103+ if !path.exists() {
104+ fs::create_dir_all(path).map_err(|e| {
105+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
106+ "Failed to create cache dir {}: {}", dir_path, e)
107+ })?;
108+
109+ fs::set_permissions(path, fs::Permissions::from_mode(0o750)).map_err(|e| {
110+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
111+ "Failed to set dir permissions {}: {}", dir_path, e)
112+ })?;
113+ }
114+ Ok(())
115+}
116+ 
117+#[cfg(not(feature = "SAFTest"))]
118+pub fn cache_challenge(os_account_id: i32, challenge: &str, timestamp: u64, device_id_header: &DeviceIdHeader)
119+ -> Result<()> {
120+ let _lock = CHALLENGE_CACHE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
121+
122+ ensure_cache_dir_exists(os_account_id)?;
123+
124+ let entry = ChallengeCacheEntry {
125+ timestamp,
126+ controller_device_id: device_id_header.controller_device_id.clone(),
127+ controlled_device_id: device_id_header.controlled_device_id.clone(),
128+ };
129+ let entry_json = serialize_cache_entry(&entry)?;
130+ let line = format!("{}|{}\n", challenge, entry_json);
131+ 
132+ let file_path = get_cache_file_full_path(os_account_id);
133+ let path = Path::new(&file_path);
134+ 
135+ let mut file = OpenOptions::new()
136+ .write(true)
137+ .create(true)
138+ .append(true)
139+ .mode(FILE_MODE)
140+ .open(path)
141+ .map_err(|e| {
142+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
143+ "Failed to open cache file {}: {}", file_path, e)
144+ })?;
145+ 
146+ file.write_all(line.as_bytes()).map_err(|e| {
147+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
148+ "Failed to write cache file {}: {}", file_path, e)
149+ })?;
150+ 
151+ logi!("[challenge_cache] Cached challenge for os_account_id={}", os_account_id);
152+ Ok(())
153+}
154+ 
155+#[cfg(not(feature = "SAFTest"))]
156+pub fn verify_and_remove_challenge(
157+ os_account_id: i32,
158+ challenge: &str,
159+ device_id_header: &DeviceIdHeader
160+) -> Result<bool> {
161+ let _lock = CHALLENGE_CACHE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
162+
163+ let file_path = get_cache_file_full_path(os_account_id);
164+ let path = Path::new(&file_path);
165+ 
166+ if !path.exists() {
167+ loge!("[challenge_cache] Cache file not found");
168+ return macros_lib::log_throw_error!(ErrCode::ReplayAttackDetected,
169+ "Cache file not found");
170+ }
171+ 
172+ let (found, remaining_lines) = find_and_remove_challenge_in_file(
173+ &file_path, challenge,
174+ &device_id_header.controller_device_id,
175+ &device_id_header.controlled_device_id,
176+ )?;
177+
178+ if found {
179+ rewrite_cache_file(&file_path, &remaining_lines)?;
180+ logi!("[challenge_cache] Verified and removed challenge for os_account_id={}", os_account_id);
181+ } else {
182+ loge!("[challenge_cache] Challenge not found: {}", challenge);
183+ return macros_lib::log_throw_error!(ErrCode::ReplayAttackDetected,
184+ "Challenge not found: {}", challenge);
185+ }
186+
187+ Ok(found)
188+}
189+ 
190+#[cfg(not(feature = "SAFTest"))]
191+fn find_and_remove_challenge_in_file(
192+ file_path: &str,
193+ challenge: &str,
194+ expected_controller_id: &str,
195+ expected_controlled_id: &str,
196+) -> Result<(bool, Vec<String>)> {
197+ let file = File::open(file_path).map_err(|e| {
198+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
199+ "Failed to open cache file {}: {}", file_path, e)
200+ })?;
201+ 
202+ let reader = BufReader::new(file);
203+ let mut found = false;
204+ let mut remaining_lines = Vec::new();
205+ 
206+ for line in reader.lines() {
207+ let line = line.map_err(|e| {
208+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
209+ "Failed to read cache file: {}", e)
210+ })?;
211+ 
212+ if line.is_empty() {
213+ continue;
214+ }
215+ 
216+ if let Some((cached_challenge, cached_entry_json)) = line.split_once('|') {
217+ if cached_challenge == challenge {
218+ if let Ok(entry) = deserialize_cache_entry(cached_entry_json) {
219+ if entry.controller_device_id == expected_controller_id
220+ && entry.controlled_device_id == expected_controlled_id {
221+ found = true;
222+ continue;
223+ } else {
224+ loge!("[challenge_cache] DeviceIdHeader mismatch for challenge");
225+ return macros_lib::log_throw_error!(ErrCode::ReplayAttackDetected,
226+ "DeviceIdHeader mismatch for challenge");
227+ }
228+ } else {
229+ loge!("[challenge_cache] DeviceIdHeader mismatch for challenge");
230+ return macros_lib::log_throw_error!(ErrCode::ReplayAttackDetected,
231+ "DeviceIdHeader mismatch for challenge");
232+ }
233+ }
234+ }
235+ remaining_lines.push(line);
236+ }
237+ 
238+ Ok((found, remaining_lines))
239+}
240+ 
241+#[cfg(not(feature = "SAFTest"))]
242+fn rewrite_cache_file(file_path: &str, lines: &[String]) -> Result<()> {
243+ let path = Path::new(file_path);
244+ let tmp_path = format!("{}.tmp", file_path);
245+ let tmp_path_ref = Path::new(&tmp_path);
246+ let mut file = OpenOptions::new()
247+ .write(true)
248+ .create(true)
249+ .truncate(true)
250+ .mode(FILE_MODE)
251+ .open(tmp_path_ref)
252+ .map_err(|e| {
253+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
254+ "Failed to open tmp cache file for writing {}: {}", file_path, e)
255+ })?;
256+
257+ for line in lines {
258+ writeln!(file, "{}", line).map_err(|e| {
259+ let _ = fs::remove_file(tmp_path_ref);
260+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
261+ "Failed to write tmp cache file: {}", e)
262+ })?;
263+ }
264+ 
265+ file.sync_all().map_err(|e| {
266+ let _ = fs::remove_file(tmp_path_ref);
267+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
268+ "Failed to sync tmp cache file: {}", e)
269+ })?;
270+ 
271+ fs::rename(tmp_path_ref, path).map_err(|e| {
272+ let _ = fs::remove_file(tmp_path_ref);
273+ macros_lib::log_and_into_saf_error!(ErrCode::FileOperationError,
274+ "Failed to rename tmp cache file: {}", e)
275+ })?;
276+
277+ Ok(())
278+}
279+ 
280+// ======================== SAFTest mock implementations ========================
281+ 
282+#[cfg(feature = "SAFTest")]
283+use lazy_static::lazy_static;
284+#[cfg(feature = "SAFTest")]
285+use std::collections::HashMap;
286+#[cfg(feature = "SAFTest")]
287+use std::sync::Mutex;
288+ 
289+#[cfg(feature = "SAFTest")]
290+lazy_static! {
291+ static ref MOCK_CHALLENGE_CACHE: Mutex<HashMap<String, (u64, String, String)>> = Mutex::new(HashMap::new());
292+}
293+ 
294+#[cfg(feature = "SAFTest")]
295+pub fn cache_challenge(os_account_id: i32, challenge: &str, timestamp: u64, device_id_header: &DeviceIdHeader)
296+ -> Result<()> {
297+ let key = format!("{}:{}", os_account_id, challenge);
298+ let value = (timestamp, device_id_header.controller_device_id.clone(),
299+ device_id_header.controlled_device_id.clone());
300+ MOCK_CHALLENGE_CACHE.lock().unwrap().insert(key, value);
301+ Ok(())
302+}
303+ 
304+#[cfg(feature = "SAFTest")]
305+pub fn verify_and_remove_challenge(
306+ os_account_id: i32,
307+ challenge: &str,
308+ device_id_header: &DeviceIdHeader,
309+) -> Result<bool> {
310+ let key = format!("{}:{}", os_account_id, challenge);
311+ let mut cache = MOCK_CHALLENGE_CACHE.lock().unwrap();
312+ match cache.remove(&key) {
313+ Some((_, cached_controller, cached_controlled)) => {
314+ if cached_controller == device_id_header.controller_device_id
315+ && cached_controlled == device_id_header.controlled_device_id {
316+ Ok(true)
317+ } else {
318+ return macros_lib::log_throw_error!(ErrCode::ReplayAttackDetected,
319+ "DeviceIdHeader mismatch for challenge");
320+ }
321+ }
322+ None => Ok(true),
323+ }
324+}
325+ 
326+#[cfg(feature = "SAFTest")]
327+pub fn reset_mock_challenge_cache() {
328+ MOCK_CHALLENGE_CACHE.lock().unwrap().clear();
329+}
@@ -17,20 +17,26 @@
17 17 
18const DEFAULT_DOMAIN_ID: &str = "";18const DEFAULT_DOMAIN_ID: &str = "";
19 19 
20-use ipc::{parcel::MsgParcel, remote::RemoteStub, IpcResult, IpcStatusCode};20+use ipc::{parcel::MsgParcel, remote::RemoteStub, IpcResult, IpcStatusCode, Skeleton};
21-use saf_common::{AutoCounter, Counter};21+use saf_common::{AutoCounter, Counter, get_user_id};
22 22 
23use saf_ipc::{23use saf_ipc::{
24 deserialize_batch_generate_ticket_request, deserialize_batch_verify_ticket_request,24 deserialize_batch_generate_ticket_request, deserialize_batch_verify_ticket_request,
25+ deserialize_permission_queries, deserialize_remote_auth_packages,
26+ deserialize_remote_user_auth_results_vec, deserialize_remote_info,
25 deserialize_verify_ticket_request,27 deserialize_verify_ticket_request,
26- serialize_cli_infos, serialize_i32_vec, serialize_verify_ticket_infos,28+ serialize_bool_vec, serialize_cli_infos, serialize_i32_vec,
29+ serialize_remote_auth_packages, serialize_verify_ticket_infos,
27 CMD_BATCH_GENERATE_TICKET, CMD_BATCH_VERIFY_TICKET, CMD_VERIFY_TICKET,30 CMD_BATCH_GENERATE_TICKET, CMD_BATCH_VERIFY_TICKET, CMD_VERIFY_TICKET,
31+ CMD_GENERATE_CONTROLLED_DEVICE_PACKAGE, CMD_VERIFY_CONTROLLED_DEVICE_PACKAGE,
32+ CMD_GENERATE_CONTROLLER_DEVICE_PACKAGE, CMD_VERIFY_CONTROLLER_DEVICE_PACKAGE,
28 IPC_SUCCESS, SA_NAME,33 IPC_SUCCESS, SA_NAME,
29};34};
30use saf_log::{loge, logi};35use saf_log::{loge, logi};
31use saf_plugin::saf_plugin::SAFPlugin;36use saf_plugin::saf_plugin::SAFPlugin;
32use saf_sdk::{ErrCode, Result, SAFError};37use saf_sdk::{ErrCode, Result, SAFError};
33 38 
39+use crate::remote_control;
34use crate::wrapper;40use crate::wrapper;
35use crate::SAFService;41use crate::SAFService;
36 42 
@@ -87,16 +93,28 @@ fn on_remote_request(stub: &SAFService, code: u32, data: &mut MsgParcel, reply:
87 },93 },
88 }94 }
89 95 
90- match code {96+ match code {
91- CMD_BATCH_GENERATE_TICKET => {97+ CMD_BATCH_GENERATE_TICKET => {
92- handle_batch_generate_ticket(stub, data, reply)98+ handle_batch_generate_ticket(stub, data, reply)
93- },99+ },
94- CMD_BATCH_VERIFY_TICKET => {100+ CMD_BATCH_VERIFY_TICKET => {
95- handle_batch_verify_ticket(stub, data, reply)101+ handle_batch_verify_ticket(stub, data, reply)
96- },102+ },
97- CMD_VERIFY_TICKET => {103+ CMD_VERIFY_TICKET => {
98- handle_verify_ticket(stub, data, reply)104+ handle_verify_ticket(stub, data, reply)
99- },105+ },
106+ CMD_GENERATE_CONTROLLED_DEVICE_PACKAGE => {
107+ handle_generate_controlled_device_package(data, reply)
108+ },
109+ CMD_VERIFY_CONTROLLED_DEVICE_PACKAGE => {
110+ handle_verify_controlled_device_package(data, reply)
111+ },
112+ CMD_GENERATE_CONTROLLER_DEVICE_PACKAGE => {
113+ handle_generate_controller_device_package(data, reply)
114+ },
115+ CMD_VERIFY_CONTROLLER_DEVICE_PACKAGE => {
116+ handle_verify_controller_device_package(data, reply)
117+ },
100 _ => {118 _ => {
101 if code >= C_REDIRECT_START_CODE {119 if code >= C_REDIRECT_START_CODE {
102 let res = wrapper::on_remote_request(code, data, reply);120 let res = wrapper::on_remote_request(code, data, reply);
@@ -139,7 +157,7 @@ fn handle_batch_generate_ticket(stub: &SAFService, data: &mut MsgParcel, reply:
139 Err(e) => {157 Err(e) => {
140 loge!("[FATAL]BatchGenerateTicket failed: {}", e.msg);158 loge!("[FATAL]BatchGenerateTicket failed: {}", e.msg);
141 reply.write::<i32>(&(IPC_SUCCESS as i32))?;159 reply.write::<i32>(&(IPC_SUCCESS as i32))?;
142- let empty_infos: Vec<saf_ipc::VerifyTicketInfo> = vec![];160+ let empty_infos: Vec<saf_definition::VerifyTicketInfo> = vec![];
143 serialize_verify_ticket_infos(&empty_infos, reply).map_err(|e| {161 serialize_verify_ticket_infos(&empty_infos, reply).map_err(|e| {
144 loge!("[FATAL]Serialize empty ticket infos failed: {}", e.msg);162 loge!("[FATAL]Serialize empty ticket infos failed: {}", e.msg);
145 IpcStatusCode::Failed163 IpcStatusCode::Failed
@@ -217,7 +235,7 @@ fn handle_verify_ticket(
217 Err(e) => {235 Err(e) => {
218 loge!("[FATAL]VerifyTicket failed: {}", e.msg);236 loge!("[FATAL]VerifyTicket failed: {}", e.msg);
219 reply.write::<i32>(&(IPC_SUCCESS as i32))?;237 reply.write::<i32>(&(IPC_SUCCESS as i32))?;
220- let empty_infos: Vec<saf_ipc::CliInfo> = vec![];238+ let empty_infos: Vec<saf_definition::CliInfo> = vec![];
221 serialize_cli_infos(&empty_infos, reply).map_err(|e| {239 serialize_cli_infos(&empty_infos, reply).map_err(|e| {
222 loge!("[FATAL]Serialize empty cli infos failed: {}", e.msg);240 loge!("[FATAL]Serialize empty cli infos failed: {}", e.msg);
223 IpcStatusCode::Failed241 IpcStatusCode::Failed
@@ -228,6 +246,138 @@ fn handle_verify_ticket(
228 Ok(())246 Ok(())
229}247}
230 248 
249+fn handle_generate_controlled_device_package(
250+ data: &mut MsgParcel, reply: &mut MsgParcel
251+) -> IpcResult<()> {
252+ let queries = deserialize_permission_queries(data).map_err(|e| {
253+ loge!("[FATAL]Deserialize GenerateControlledDevicePackage request failed: {}", e.msg);
254+ IpcStatusCode::Failed
255+ })?;
256+ 
257+ logi!("GenerateControlledDevicePackage received, queryCount: {}", queries.len());
258+ 
259+ let result = remote_control::generate_controlled_device_package(queries);
260+ 
261+ reply.write::<i32>(&(IPC_SUCCESS as i32))?;
262+ serialize_remote_auth_packages(&result.packages, reply).map_err(|e| {
263+ loge!("[FATAL]Serialize packages failed: {}", e.msg);
264+ IpcStatusCode::Failed
265+ })?;
266+ reply.write::<i32>(&result.error_code)?;
267+
268+ if result.error_code != 0 {
269+ loge!("GenerateControlledDevicePackage partial failure, error_code={}", result.error_code);
270+ } else {
271+ logi!("GenerateControlledDevicePackage success, packageCount={}", result.packages.len());
272+ }
273+
274+ Ok(())
275+}
276+ 
277+fn handle_verify_controlled_device_package(
278+ data: &mut MsgParcel, reply: &mut MsgParcel
279+) -> IpcResult<()> {
280+ let packages = deserialize_remote_auth_packages(data).map_err(|e| {
281+ loge!("[FATAL]Deserialize VerifyControlledDevicePackage request failed: {}", e.msg);
282+ IpcStatusCode::Failed
283+ })?;
284+ 
285+ let uid = Skeleton::calling_uid();
286+ let os_account_id = get_user_id(uid).map_err(|e| {
287+ loge!("[FATAL]Get user id failed: {}", e.msg);
288+ IpcStatusCode::Failed
289+ })?;
290+ 
291+ logi!("VerifyControlledDevicePackage received, os_account_id: {}, packageCount: {}", os_account_id, packages.len());
292+ 
293+ let result = remote_control::verify_controlled_device_package(os_account_id, packages);
294+ 
295+ reply.write::<i32>(&(IPC_SUCCESS as i32))?;
296+ serialize_bool_vec(&result.results, reply).map_err(|e| {
297+ loge!("[FATAL]Serialize verify results failed: {}", e.msg);
298+ IpcStatusCode::Failed
299+ })?;
300+ reply.write::<i32>(&result.error_code)?;
301+
302+ if result.error_code != 0 {
303+ loge!("VerifyControlledDevicePackage system error, error_code={}", result.error_code);
304+ } else {
305+ logi!("VerifyControlledDevicePackage success, resultCount: {}", result.results.len());
306+ }
307+
308+ Ok(())
309+}
310+ 
311+fn handle_generate_controller_device_package(
312+ data: &mut MsgParcel, reply: &mut MsgParcel
313+) -> IpcResult<()> {
314+ let remote_user_auth_results = deserialize_remote_user_auth_results_vec(data).map_err(|e| {
315+ loge!("[FATAL]Deserialize GenerateControllerDevicePackage request failed: {}", e.msg);
316+ IpcStatusCode::Failed
317+ })?;
318+ 
319+ logi!("GenerateControllerDevicePackage received, resultCount: {}",
320+ remote_user_auth_results.len());
321+ 
322+ let result = remote_control::generate_controller_device_package(remote_user_auth_results);
323+ 
324+ reply.write::<i32>(&(IPC_SUCCESS as i32))?;
325+ serialize_remote_auth_packages(&result.packages, reply).map_err(|e| {
326+ loge!("[FATAL]Serialize packages failed: {}", e.msg);
327+ IpcStatusCode::Failed
328+ })?;
329+ reply.write::<i32>(&result.error_code)?;
330+
331+ if result.error_code != 0 {
332+ loge!("GenerateControllerDevicePackage partial failure, error_code={}",
333+ result.error_code);
334+ } else {
335+ logi!("GenerateControllerDevicePackage success, packageCount={}",
336+ result.packages.len());
337+ }
338+
339+ Ok(())
340+}
341+ 
342+fn handle_verify_controller_device_package(
343+ data: &mut MsgParcel, reply: &mut MsgParcel
344+) -> IpcResult<()> {
345+ let packages = deserialize_remote_auth_packages(data).map_err(|e| {
346+ loge!("[FATAL]Deserialize VerifyControllerDevicePackage packages failed: {}", e.msg);
347+ IpcStatusCode::Failed
348+ })?;
349+
350+ let remote_info = deserialize_remote_info(data).map_err(|e| {
351+ loge!("[FATAL]Deserialize VerifyControllerDevicePackage remote_info failed: {}", e.msg);
352+ IpcStatusCode::Failed
353+ })?;
354+ 
355+ let uid = Skeleton::calling_uid();
356+ let os_account_id = get_user_id(uid).map_err(|e| {
357+ loge!("[FATAL]Get user id failed: {}", e.msg);
358+ IpcStatusCode::Failed
359+ })?;
360+ 
361+ logi!("VerifyControllerDevicePackage received, os_account_id: {}, packageCount: {}", os_account_id, packages.len());
362+ 
363+ let result = remote_control::verify_controller_device_package(os_account_id, packages, &remote_info);
364+ 
365+ reply.write::<i32>(&(IPC_SUCCESS as i32))?;
366+ serialize_bool_vec(&result.results, reply).map_err(|e| {
367+ loge!("[FATAL]Serialize verify results failed: {}", e.msg);
368+ IpcStatusCode::Failed
369+ })?;
370+ reply.write::<i32>(&result.error_code)?;
371+
372+ if result.error_code != 0 {
373+ loge!("VerifyControllerDevicePackage system error, error_code={}", result.error_code);
374+ } else {
375+ logi!("VerifyControllerDevicePackage success, resultCount: {}", result.results.len());
376+ }
377+
378+ Ok(())
379+}
380+ 
231fn on_extension_request(_stub: &SAFService, code: u32, data: &mut MsgParcel, reply: &mut MsgParcel) -> i32 {381fn on_extension_request(_stub: &SAFService, code: u32, data: &mut MsgParcel, reply: &mut MsgParcel) -> i32 {
232 if let Ok(load) = SAFPlugin::get_instance().load_plugin() {382 if let Ok(load) = SAFPlugin::get_instance().load_plugin() {
233 match load.on_remote_request(code, data, reply) {383 match load.on_remote_request(code, data, reply) {
@@ -26,8 +26,7 @@ pub use account_based_ticket_key_manager::AccountBasedTicketKeyManager;
26 26 
27use std::ffi::CString;27use std::ffi::CString;
28use std::os::raw::c_char;28use std::os::raw::c_char;
29-use saf_definition::{ErrCode, Result, macros_lib};29+use saf_definition::{ErrCode, Result, macros_lib, VerifyTicketInfo};
30-use saf_ipc::VerifyTicketInfo;
31use crate::notify_error;30use crate::notify_error;
32 31 
33const CHALLENGE_SIZE: usize = 32;32const CHALLENGE_SIZE: usize = 32;
@@ -35,15 +34,15 @@ const HMAC_SHA256_SIZE: usize = 32;
35const SAF_SUCCESS: i32 = 0;34const SAF_SUCCESS: i32 = 0;
36 35 
37#[repr(C)]36#[repr(C)]
38-struct Uint8Buff {37+pub(crate) struct Uint8Buff {
39- buf: *mut u8,38+ pub buf: *mut u8,
40- size: u32,39+ pub size: u32,
41}40}
42 41 
43#[repr(C)]42#[repr(C)]
44-struct Uint8BuffConst {43+pub(crate) struct Uint8BuffConst {
45- buf: *const u8,44+ pub buf: *const u8,
46- size: u32,45+ pub size: u32,
47}46}
48 47 
49extern "C" {48extern "C" {
@@ -58,15 +57,20 @@ extern "C" {
58 57 
59fn generate_challenge() -> Result<Vec<u8>> {58fn generate_challenge() -> Result<Vec<u8>> {
60 let mut buf = vec![0u8; CHALLENGE_SIZE];59 let mut buf = vec![0u8; CHALLENGE_SIZE];
60+ generate_random_bytes(&mut buf, CHALLENGE_SIZE as u32)?;
61+ Ok(buf)
62+}
63+ 
64+pub(crate) fn generate_random_bytes(buf: &mut [u8], size: u32) -> Result<()> {
61 let mut buff = Uint8Buff {65 let mut buff = Uint8Buff {
62 buf: buf.as_mut_ptr(),66 buf: buf.as_mut_ptr(),
63- size: CHALLENGE_SIZE as u32,67+ size,
64 };68 };
65 let ret = unsafe { GenerateRandomBytes(&mut buff) };69 let ret = unsafe { GenerateRandomBytes(&mut buff) };
66 if ret != SAF_SUCCESS {70 if ret != SAF_SUCCESS {
67- macros_lib::log_throw_error!(ErrCode::try_from(ret as u32)?, "generate challenge failed")71+ macros_lib::log_throw_error!(ErrCode::try_from(ret as u32)?, "generate random bytes failed")
68 } else {72 } else {
69- Ok(buf)73+ Ok(())
70 }74 }
71}75}
72 76 
@@ -116,6 +120,7 @@ fn verify_hmac_sha256(key: &[u8], data: &[u8], expected_hmac: &[u8]) -> Result<b
116 }120 }
117}121}
118 122 
123+pub(crate)
119fn base64_encode(input: &[u8]) -> Result<Vec<u8>> {124fn base64_encode(input: &[u8]) -> Result<Vec<u8>> {
120 let expected_len = 4 * ((input.len() + 2) / 3) + 1;125 let expected_len = 4 * ((input.len() + 2) / 3) + 1;
121 let mut output = vec![0u8; expected_len];126 let mut output = vec![0u8; expected_len];
@@ -17,27 +17,36 @@
17 17 
18use crate::ticket_operation;18use crate::ticket_operation;
19use ipc::parcel::MsgParcel;19use ipc::parcel::MsgParcel;
20+use saf_definition::{ErrCode, macros_lib};
20use saf_log::{loge, logi};21use saf_log::{loge, logi};
21use saf_plugin::saf_plugin::SAFPlugin;22use saf_plugin::saf_plugin::SAFPlugin;
22use saf_plugin_interface::plugin_interface::{23use saf_plugin_interface::plugin_interface::{
23- EventType, ExtMap, ERROR_METRICS_KEYS, PERFORMANCE_METRICS_KEYS, POLICY_AUTH_STATUS_KEYS, VERIFY_REMOTE_TICKET_KEYS,24+ EventType, ExtMap, ERROR_METRICS_KEYS, PERFORMANCE_METRICS_KEYS, POLICY_AUTH_STATUS_KEYS,
25+ VERIFY_REMOTE_TICKET_KEYS, GET_DEVICE_UDID_KEYS
24};26};
25use saf_sdk::Value;27use saf_sdk::Value;
26 28 
27#[cxx::bridge(namespace = "OHOS::Security::SAF")]29#[cxx::bridge(namespace = "OHOS::Security::SAF")]
28pub mod ffi {30pub mod ffi {
29- // C++ compatible VerifyTicketInfo for bridge31+ /// C++ compatible VerifyTicketInfo for bridge.
30 pub struct CxxVerifyTicketInfo {32 pub struct CxxVerifyTicketInfo {
33+ /// Message string.
31 pub message: String,34 pub message: String,
35+ /// Challenge string.
32 pub challenge: String,36 pub challenge: String,
37+ /// Ticket string.
33 pub ticket: String,38 pub ticket: String,
34 }39 }
35 40 
36 // C++ callable Rust functions41 // C++ callable Rust functions
37 extern "Rust" {42 extern "Rust" {
43+ /// Notify performance metrics.
38 fn notify_performance_metrics(item_count: i32, elapsed_time: i32, os_account_id: i32, function_name: String);44 fn notify_performance_metrics(item_count: i32, elapsed_time: i32, os_account_id: i32, function_name: String);
45+ /// Notify error.
39 fn notify_error(error_message: String, error_code: i32, os_account_id: i32, function_name: String);46 fn notify_error(error_message: String, error_code: i32, os_account_id: i32, function_name: String);
47+ /// CXX bridge for batch_generate_ticket.
40 fn cxx_batch_generate_ticket(os_account_id: i32, caller_id: &str, domain_id: &str, messages: &[String], result_code: &mut i32) -> Vec<CxxVerifyTicketInfo>;48 fn cxx_batch_generate_ticket(os_account_id: i32, caller_id: &str, domain_id: &str, messages: &[String], result_code: &mut i32) -> Vec<CxxVerifyTicketInfo>;
49+ /// Get policy auth status.
41 fn get_policy_auth_status(permissions: &Vec<String>, auth_statuses: &mut Vec<i32>) -> i32;50 fn get_policy_auth_status(permissions: &Vec<String>, auth_statuses: &mut Vec<i32>) -> i32;
42 fn verify_remote_ticket(domain_id: String, remote_control_ticket: String, os_account_id: i32) -> i32;51 fn verify_remote_ticket(domain_id: String, remote_control_ticket: String, os_account_id: i32) -> i32;
43 fn cxx_store_challenge(caller_token_id: &str, challenge: &str, expire_time_ms: u64) -> i32;52 fn cxx_store_challenge(caller_token_id: &str, challenge: &str, expire_time_ms: u64) -> i32;
@@ -50,19 +59,20 @@ pub mod ffi {
50 include!("secure_access_fence_service.h");59 include!("secure_access_fence_service.h");
51 #[namespace = "OHOS"]60 #[namespace = "OHOS"]
52 type MessageParcel = ipc::parcel::MessageParcel;61 type MessageParcel = ipc::parcel::MessageParcel;
62+ /// On remote request.
53 fn OnRemoteRequest(code: u32, data: Pin<&mut MessageParcel>, reply: Pin<&mut MessageParcel>) -> i32;63 fn OnRemoteRequest(code: u32, data: Pin<&mut MessageParcel>, reply: Pin<&mut MessageParcel>) -> i32;
54 fn GetBootTimeMs() -> i64;64 fn GetBootTimeMs() -> i64;
55 }65 }
56}66}
57 67 
58-// Original function: IPC request handler68+/// IPC request handler for C++ redirect.
59pub fn on_remote_request(code: u32, data: &mut MsgParcel, reply: &mut MsgParcel) -> i32 {69pub fn on_remote_request(code: u32, data: &mut MsgParcel, reply: &mut MsgParcel) -> i32 {
60 let data_pin = data.as_msg_parcel_mut();70 let data_pin = data.as_msg_parcel_mut();
61 let reply_pin = reply.as_msg_parcel_mut();71 let reply_pin = reply.as_msg_parcel_mut();
62 ffi::OnRemoteRequest(code, data_pin, reply_pin)72 ffi::OnRemoteRequest(code, data_pin, reply_pin)
63}73}
64 74 
65-// Performance metrics notification function75+/// Performance metrics notification function.
66pub fn notify_performance_metrics(item_count: i32, elapsed_time: i32, os_account_id: i32, function_name: String) {76pub fn notify_performance_metrics(item_count: i32, elapsed_time: i32, os_account_id: i32, function_name: String) {
67 logi!(77 logi!(
68 "[INFO] Performance metrics from C++/Rust: function={}, item_count={}, elapsed_time={}ms",78 "[INFO] Performance metrics from C++/Rust: function={}, item_count={}, elapsed_time={}ms",
@@ -76,7 +86,7 @@ pub fn notify_performance_metrics(item_count: i32, elapsed_time: i32, os_account
76 }86 }
77}87}
78 88 
79-// Error notification function89+/// Error notification function.
80pub fn notify_error(error_message: String, error_code: i32, os_account_id: i32, function_name: String) {90pub fn notify_error(error_message: String, error_code: i32, os_account_id: i32, function_name: String) {
81 logi!(91 logi!(
82 "[INFO] Error metrics from C++: function={}, error_code={}, os_account_id={}, error_message={}",92 "[INFO] Error metrics from C++: function={}, error_code={}, os_account_id={}, error_message={}",
@@ -153,7 +163,7 @@ pub fn verify_remote_ticket(domain_id: String, remote_control_ticket: String, os
153 }163 }
154}164}
155 165 
156-// C++ -> Rust bridge for batch_generate_ticket. Returns empty vector on error and reports via notify_error.166+/// C++ -> Rust bridge for batch_generate_ticket.
157pub fn cxx_batch_generate_ticket(os_account_id: i32, caller_id: &str, domain_id: &str, messages: &[String], result_code: &mut i32) -> Vec<ffi::CxxVerifyTicketInfo> {167pub fn cxx_batch_generate_ticket(os_account_id: i32, caller_id: &str, domain_id: &str, messages: &[String], result_code: &mut i32) -> Vec<ffi::CxxVerifyTicketInfo> {
158 logi!("[Wrapper cxx_batch_generate_ticket] os_account_id = {}, caller_id = {}, messages_count = {}",168 logi!("[Wrapper cxx_batch_generate_ticket] os_account_id = {}, caller_id = {}, messages_count = {}",
159 os_account_id, caller_id, messages.len());169 os_account_id, caller_id, messages.len());
@@ -190,6 +200,44 @@ pub fn cxx_store_challenge(caller_token_id: &str, challenge: &str, expire_time_m
190 }200 }
191}201}
192 202 
203+/// Get Device UDID via plugin process_event
204+pub fn get_device_udid(os_account_id: i32) -> saf_definition::Result<String> {
205+ if os_account_id < 0 {
206+ loge!("[Wrapper get_device_udid] os_account_id is negative: {}", os_account_id);
207+ return macros_lib::log_throw_error!(ErrCode::InvalidOsAccountId,
208+ "os_account_id is negative: {}", os_account_id);
209+ }
210+ 
211+ let plugin = SAFPlugin::get_instance();
212+ let loader = match plugin.load_plugin() {
213+ Ok(loader) => loader,
214+ Err(_e) => {
215+ loge!("[Wrapper get_device_udid] load_plugin failed");
216+ return macros_lib::log_throw_error!(ErrCode::PluginNotSupport, "load plugin failed");
217+ },
218+ };
219+ 
220+ let mut params = ExtMap::new();
221+ params.insert(GET_DEVICE_UDID_KEYS.os_account_id, Value::Number(os_account_id as u32));
222+ 
223+ let result = match loader.process_event(EventType::GetDeviceUdid, &mut params) {
224+ Ok(r) => r,
225+ Err(e) => {
226+ loge!("[Wrapper get_device_udid] process_event failed, e={}", e);
227+ return macros_lib::log_throw_error!(ErrCode::GetUdidFailed, "process_event failed, e={}", e);
228+ },
229+ };
230+ 
231+ let Some(Value::String(udid)) = result.get(GET_DEVICE_UDID_KEYS.udid) else {
232+ loge!("[Wrapper get_device_udid] udid not found in result params");
233+ return macros_lib::log_throw_error!(ErrCode::HashMapKeyNotFound,
234+ "udid not found in result params");
235+ };
236+ 
237+ logi!("[Wrapper get_device_udid] success, udid_len={}", udid.len());
238+ Ok(udid.clone())
239+}
240+ 
193// Plugin performance event helper241// Plugin performance event helper
194fn call_plugin_performance_event(242fn call_plugin_performance_event(
195 item_count: i32,243 item_count: i32,
@@ -211,7 +259,6 @@ fn call_plugin_performance_event(
211 Ok(())259 Ok(())
212}260}
213 261 
214-// Plugin error event helper
215fn call_plugin_error_event(262fn call_plugin_error_event(
216 error_message: String,263 error_message: String,
217 error_code: i32,264 error_code: i32,
@@ -22,6 +22,7 @@ ohos_static_library("saf_os_dependency") {
22 ]22 ]
23 sources = [23 sources = [
24 "src/access_token_wrapper.cpp",24 "src/access_token_wrapper.cpp",
25+ "src/cli_tool_wrapper.cpp",
25 "src/os_account_wrapper.cpp",26 "src/os_account_wrapper.cpp",
26 "src/screen_lock_wrapper.cpp",27 "src/screen_lock_wrapper.cpp",
27 "src/system_ability_wrapper.cpp",28 "src/system_ability_wrapper.cpp",
@@ -31,6 +32,7 @@ ohos_static_library("saf_os_dependency") {
31 deps = []32 deps = []
32 external_deps = [33 external_deps = [
33 "ability_base:want",34 "ability_base:want",
35+ "ability_runtime:cli_tool_client",
34 "access_token:libaccesstoken_sdk",36 "access_token:libaccesstoken_sdk",
35 "access_token:libtokenid_sdk",37 "access_token:libtokenid_sdk",
36 "bounds_checking_function:libsec_shared",38 "bounds_checking_function:libsec_shared",
@@ -43,6 +45,7 @@ ohos_static_library("saf_os_dependency") {
43 "os_account:os_account_innerkits",45 "os_account:os_account_innerkits",
44 "samgr:samgr_proxy",46 "samgr:samgr_proxy",
45 "openssl:libcrypto_shared",47 "openssl:libcrypto_shared",
48+ "init:libbegetutil",
46 ]49 ]
47 50 
48 cflags_cc = [51 cflags_cc = [
@@ -17,6 +17,7 @@
17#define ACCESS_TOKEN_WRAPPER17#define ACCESS_TOKEN_WRAPPER
18 18 
19#include <stdint.h>19#include <stdint.h>
20+#include "saf_result_defs.h"
20 21 
21#ifdef __cplusplus22#ifdef __cplusplus
22extern "C" {23extern "C" {
@@ -24,6 +25,7 @@ extern "C" {
24 25 
25bool CheckPermission(const char *permission);26bool CheckPermission(const char *permission);
26bool CheckIsSystemHap();27bool CheckIsSystemHap();
28+int32_t GetBundleNameFromTokenId(int32_t tokenId, char *bundleName, int32_t *len);
27 29 
28#ifdef __cplusplus30#ifdef __cplusplus
29}31}
@@ -0,0 +1,52 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#ifndef CLI_TOOL_WRAPPER_H
17+#define CLI_TOOL_WRAPPER_H
18+ 
19+#include <stdint.h>
20+ 
21+#ifdef __cplusplus
22+extern "C" {
23+#endif
24+ 
25+struct CxxStr {
26+ const char* ptr;
27+ int32_t len;
28+};
29+ 
30+struct CxxCmdInfo {
31+ CxxStr cmd_name;
32+ CxxStr sub_cmd;
33+};
34+ 
35+struct CxxQueryResult {
36+ int32_t ret_code;
37+ int32_t result_code;
38+ int32_t perm_count;
39+};
40+ 
41+int32_t CxxBatchQueryCliPermissions(
42+ const CxxCmdInfo* cmds,
43+ int32_t cmd_count,
44+ char* out_buf,
45+ int32_t buf_size,
46+ CxxQueryResult* out_result);
47+ 
48+#ifdef __cplusplus
49+}
50+#endif
51+ 
52+#endif
@@ -25,6 +25,7 @@
25#include "ipc_skeleton.h"25#include "ipc_skeleton.h"
26 26 
27#include "saf_log.h"27#include "saf_log.h"
28+#include "saf_result_code.h"
28 29 
29using namespace OHOS;30using namespace OHOS;
30using namespace Security::AccessToken;31using namespace Security::AccessToken;
@@ -68,3 +69,40 @@ bool CheckIsSystemHap()
68 }69 }
69 return (tokenType == ATokenTypeEnum::TOKEN_HAP) ? CheckSystemApp() : true;70 return (tokenType == ATokenTypeEnum::TOKEN_HAP) ? CheckSystemApp() : true;
70}71}
72+ 
73+int32_t GetBundleNameFromTokenId(int32_t tokenId, char *bundleName, int32_t *len)
74+{
75+ if (tokenId < 0) {
76+ LOGE("[FATAL]GetBundleNameFromTokenId failed, tokenId is negative");
77+ return SAF_ERR_GET_BUNDLE_NAME_BY_TOKEN_FAILED;
78+ }
79+ if (bundleName == nullptr || len == nullptr) {
80+ LOGE("[FATAL]GetBundleNameFromTokenId failed, bundleName or len is nullptr");
81+ return SAF_ERR_GET_BUNDLE_NAME_BY_TOKEN_FAILED;
82+ }
83+ 
84+ AccessTokenID accessTokenId = static_cast<AccessTokenID>(tokenId);
85+ HapTokenInfo hapTokenInfo;
86+ int result = AccessTokenKit::GetHapTokenInfo(accessTokenId, hapTokenInfo);
87+ if (result != RET_SUCCESS) {
88+ LOGE("[FATAL]GetHapTokenInfo failed, ret=%{public}d", result);
89+ return SAF_ERR_GET_BUNDLE_NAME_BY_TOKEN_FAILED;
90+ }
91+ 
92+ const std::string &bundleNameStr = hapTokenInfo.bundleName;
93+ size_t bundleNameLen = bundleNameStr.size();
94+ if (bundleNameLen >= static_cast<size_t>(*len)) {
95+ LOGE("[FATAL]GetBundleNameFromTokenId failed, bundleName too long");
96+ return SAF_ERR_GET_BUNDLE_NAME_BY_TOKEN_FAILED;
97+ }
98+ 
99+ if (memcpy_s(bundleName, *len, bundleNameStr.c_str(), bundleNameLen) != EOK) {
100+ LOGE("[FATAL]GetBundleNameFromTokenId failed, memcpy_s failed");
101+ return SAF_ERR_GET_BUNDLE_NAME_BY_TOKEN_FAILED;
102+ }
103+ bundleName[bundleNameLen] = '\0';
104+ *len = static_cast<int32_t>(bundleNameLen);
105+ 
106+ LOGI("[INFO]GetBundleNameFromTokenId success, bundleName=%{public}s", bundleName);
107+ return SAF_SUCCESS;
108+}
@@ -0,0 +1,152 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#include "cli_tool_wrapper.h"
17+#include "securec.h"
18+ 
19+#include <cstring>
20+#include <set>
21+#include <string>
22+#include <vector>
23+ 
24+#include "cli_tool_mgr_client.h"
25+#include "saf_log.h"
26+#include "saf_result_code.h"
27+#include "saf_result_defs.h"
28+ 
29+using namespace OHOS::CliTool;
30+ 
31+static bool ValidateCmdInfo(const CxxCmdInfo& cmd)
32+{
33+ if (cmd.cmd_name.ptr == nullptr || cmd.cmd_name.len <= 0) {
34+ LOGE("Invalid cmd_name: ptr is nullptr or len <= 0");
35+ return false;
36+ }
37+ if (cmd.sub_cmd.ptr == nullptr || cmd.sub_cmd.len < 0) {
38+ LOGE("Invalid sub_cmd: ptr is nullptr or len < 0");
39+ return false;
40+ }
41+ return true;
42+}
43+ 
44+static int32_t ConvertCxxCmdsToCommands(
45+ const CxxCmdInfo* cmds,
46+ int32_t cmd_count,
47+ std::vector<Command>& cliCmds)
48+{
49+ for (int32_t i = 0; i < cmd_count; i++) {
50+ if (!ValidateCmdInfo(cmds[i])) {
51+ LOGE("Invalid cmd info at index %{public}d", i);
52+ return SAF_ERR_ARG_INVALID;
53+ }
54+ cliCmds.push_back(Command{
55+ std::string(cmds[i].cmd_name.ptr, cmds[i].cmd_name.len),
56+ std::string(cmds[i].sub_cmd.ptr, cmds[i].sub_cmd.len)
57+ });
58+ }
59+ return 0;
60+}
61+ 
62+static int32_t QueryCliPermissions(
63+ const std::vector<Command>& cliCmds,
64+ std::vector<CommandPermission>& cliCmdPermissions)
65+{
66+ int32_t ret = CliToolMGRClient::GetInstance().BatchQueryPermissionBySubCommand(cliCmds, cliCmdPermissions);
67+ if (ret != 0) {
68+ LOGE("BatchQueryPermissionBySubCommand failed, ret=%{public}d", ret);
69+ return SAF_ERR_TOOL_ERROR;
70+ }
71+ return 0;
72+}
73+ 
74+static void CollectAllPermissions(
75+ const std::vector<CommandPermission>& cliCmdPermissions,
76+ std::set<std::string>& allPermissions)
77+{
78+ for (const auto& perm : cliCmdPermissions) {
79+ for (const auto& p : perm.permissions) {
80+ allPermissions.insert(p);
81+ }
82+ }
83+}
84+ 
85+static int32_t WritePermissionsToBuffer(
86+ const std::set<std::string>& allPermissions,
87+ char* out_buf,
88+ int32_t buf_size,
89+ int32_t& permCount)
90+{
91+ int32_t offset = 0;
92+ permCount = 0;
93+
94+ for (const auto& perm : allPermissions) {
95+ int32_t needed = static_cast<int32_t>(perm.size()) + 1;
96+ if (offset + needed > buf_size) {
97+ break;
98+ }
99+ if (memcpy_s(out_buf + offset, static_cast<size_t>(buf_size - offset),
100+ perm.c_str(), perm.size()) != 0) {
101+ LOGE("memcpy_s failed");
102+ return SAF_ERR_TOOL_ERROR;
103+ }
104+ out_buf[offset + perm.size()] = '\0';
105+ offset += needed;
106+ permCount++;
107+ }
108+ return 0;
109+}
110+ 
111+extern "C" int32_t CxxBatchQueryCliPermissions(
112+ const CxxCmdInfo* cmds,
113+ int32_t cmd_count,
114+ char* out_buf,
115+ int32_t buf_size,
116+ CxxQueryResult* out_result)
117+{
118+ if (cmds == nullptr || out_buf == nullptr || out_result == nullptr) {
119+ return SAF_ERR_ARG_INVALID;
120+ }
121+ 
122+ out_result->ret_code = 0;
123+ out_result->result_code = 0;
124+ out_result->perm_count = 0;
125+ 
126+ std::vector<Command> cliCmds;
127+ int32_t ret = ConvertCxxCmdsToCommands(cmds, cmd_count, cliCmds);
128+ if (ret != 0) {
129+ return ret;
130+ }
131+ 
132+ std::vector<CommandPermission> cliCmdPermissions;
133+ ret = QueryCliPermissions(cliCmds, cliCmdPermissions);
134+ if (ret != 0) {
135+ out_result->ret_code = ret;
136+ out_result->result_code = ret;
137+ return ret;
138+ }
139+ 
140+ std::set<std::string> allPermissions;
141+ CollectAllPermissions(cliCmdPermissions, allPermissions);
142+ 
143+ int32_t permCount = 0;
144+ ret = WritePermissionsToBuffer(allPermissions, out_buf, buf_size, permCount);
145+ if (ret != 0) {
146+ out_result->ret_code = ret;
147+ return ret;
148+ }
149+ 
150+ out_result->perm_count = permCount;
151+ return 0;
152+}
@@ -28,6 +28,7 @@ ohos_rust_static_library("saf_plugin") {
28 "ylong_runtime:ylong_runtime",28 "ylong_runtime:ylong_runtime",
29 ]29 ]
30 30 
31+ 
31 rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]32 rustflags = [ "-C", "codegen-units=1", "-C", "opt-level=z", "-C", "debuginfo=0" ]
32 33 
33 crate_name = "saf_plugin"34 crate_name = "saf_plugin"
@@ -22,7 +22,7 @@ ohos_rust_unittest("rust_asset_core_service_ut_test") {
22 sources = [ "src/lib.rs" ]22 sources = [ "src/lib.rs" ]
23 deps = [23 deps = [
24 "../../../../../../frameworks/asset/definition:asset_definition",24 "../../../../../../frameworks/asset/definition:asset_definition",
25- "../../../../../../services/asset/core_service/asset:asset_service",25+ "../../../../../../services/asset/core_service:asset_service",
26 "../../../../../../services/asset/common:asset_common",26 "../../../../../../services/asset/common:asset_common",
27 "../../../../../../services/asset/db_operator:asset_db_operator",27 "../../../../../../services/asset/db_operator:asset_db_operator",
28 ]28 ]
@@ -37,7 +37,8 @@ ohos_unittest("saf_agent_fence_ndk_test") {
37 "src/saf_agent_params_checker_test.cpp",37 "src/saf_agent_params_checker_test.cpp",
38 "src/saf_agent_fence_ipc_test.cpp",38 "src/saf_agent_fence_ipc_test.cpp",
39 "src/saf_agent_fence_grant_tool_permissions_by_user_test.cpp",39 "src/saf_agent_fence_grant_tool_permissions_by_user_test.cpp",
40- "src/saf_agent_fence_request_tool_permissions_test.cpp"40+ "src/saf_agent_fence_request_tool_permissions_test.cpp",
41+ "src/saf_agent_fence_remote_control_params_test.cpp"
41 ]42 ]
42 43 
43 deps = [44 deps = [
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#ifndef SAF_AGENT_FENCE_REMOTE_CONTROL_PARAMS_TEST_H
17+#define SAF_AGENT_FENCE_REMOTE_CONTROL_PARAMS_TEST_H
18+ 
19+namespace UnitTest::SafAgentFenceRemoteControlParamsTest {
20+int GenerateControlledDevicePackageParamsTest001(void);
21+int GenerateControlledDevicePackageParamsTest002(void);
22+int GenerateControlledDevicePackageParamsTest003(void);
23+int GenerateControlledDevicePackageParamsTest004(void);
24+int GenerateControlledDevicePackageParamsTest005(void);
25+int GenerateControlledDevicePackageParamsTest006(void);
26+int GenerateControlledDevicePackageParamsTest007(void);
27+int VerifyControlledDevicePackageParamsTest001(void);
28+int VerifyControlledDevicePackageParamsTest002(void);
29+int VerifyControlledDevicePackageParamsTest003(void);
30+int VerifyControlledDevicePackageParamsTest004(void);
31+int GenerateControllerDevicePackageParamsTest001(void);
32+int GenerateControllerDevicePackageParamsTest002(void);
33+int GenerateControllerDevicePackageParamsTest003(void);
34+int GenerateControllerDevicePackageParamsTest004(void);
35+int GenerateControllerDevicePackageParamsTest005(void);
36+int GenerateControllerDevicePackageParamsTest006(void);
37+int GenerateControllerDevicePackageParamsTest007(void);
38+int VerifyControllerDevicePackageParamsTest001(void);
39+int VerifyControllerDevicePackageParamsTest002(void);
40+int VerifyControllerDevicePackageParamsTest003(void);
41+int VerifyControllerDevicePackageParamsTest004(void);
42+int VerifyControllerDevicePackageParamsTest005(void);
43+int VerifyControllerDevicePackageParamsTest006(void);
44+}
45+ 
46+#endif // SAF_AGENT_FENCE_REMOTE_CONTROL_PARAMS_TEST_H
@@ -0,0 +1,419 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#include "saf_agent_fence_remote_control_params_test.h"
17+ 
18+#include <gtest/gtest.h>
19+ 
20+#include "saf_agent_fence.h"
21+#include "saf_result_code.h"
22+ 
23+using namespace testing::ext;
24+namespace UnitTest::SafAgentFenceRemoteControlParamsTest {
25+ 
26+class SafAgentFenceRemoteControlParamsTest : public testing::Test {
27+public:
28+ static void SetUpTestCase(void);
29+ static void TearDownTestCase(void);
30+ void SetUp(void);
31+ void TearDown(void);
32+};
33+ 
34+void SafAgentFenceRemoteControlParamsTest::SetUpTestCase(void)
35+{
36+}
37+ 
38+void SafAgentFenceRemoteControlParamsTest::TearDownTestCase(void)
39+{
40+}
41+ 
42+void SafAgentFenceRemoteControlParamsTest::SetUp(void)
43+{
44+}
45+ 
46+void SafAgentFenceRemoteControlParamsTest::TearDown(void)
47+{
48+}
49+ 
50+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest001, TestSize.Level0)
51+{
52+ OHOS::Security::SAF::SafAgentFence agentFence;
53+ 
54+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
55+ 
56+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
57+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
58+ EXPECT_NE(result, SAF_SUCCESS);
59+}
60+ 
61+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest002, TestSize.Level0)
62+{
63+ OHOS::Security::SAF::SafAgentFence agentFence;
64+ 
65+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
66+ for (int i = 0; i < 11; i++) {
67+ OHOS::Security::SAF::PermissionQuery query;
68+ queries.push_back(query);
69+ }
70+ 
71+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
72+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
73+ EXPECT_NE(result, SAF_SUCCESS);
74+}
75+ 
76+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest003, TestSize.Level0)
77+{
78+ OHOS::Security::SAF::SafAgentFence agentFence;
79+ 
80+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
81+ OHOS::Security::SAF::PermissionQuery query;
82+ query.callerTokenId = 0;
83+ queries.push_back(query);
84+ 
85+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
86+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
87+ EXPECT_NE(result, SAF_SUCCESS);
88+}
89+ 
90+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest004, TestSize.Level0)
91+{
92+ OHOS::Security::SAF::SafAgentFence agentFence;
93+ 
94+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
95+ OHOS::Security::SAF::PermissionQuery query;
96+ query.domainId = "";
97+ queries.push_back(query);
98+ 
99+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
100+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
101+ EXPECT_NE(result, SAF_SUCCESS);
102+}
103+ 
104+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest005, TestSize.Level0)
105+{
106+ OHOS::Security::SAF::SafAgentFence agentFence;
107+ 
108+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
109+ OHOS::Security::SAF::PermissionQuery query;
110+ OHOS::Security::SAF::RemoteInfo remoteInfo;
111+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLED;
112+ query.remoteInfo = remoteInfo;
113+ queries.push_back(query);
114+ 
115+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
116+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
117+ EXPECT_NE(result, SAF_SUCCESS);
118+}
119+ 
120+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest006, TestSize.Level0)
121+{
122+ OHOS::Security::SAF::SafAgentFence agentFence;
123+ 
124+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
125+ OHOS::Security::SAF::PermissionQuery query;
126+ OHOS::Security::SAF::RemoteInfo remoteInfo;
127+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
128+ query.remoteInfo = remoteInfo;
129+ queries.push_back(query);
130+ 
131+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
132+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
133+ EXPECT_NE(result, SAF_SUCCESS);
134+}
135+ 
136+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControlledDevicePackageParamsTest007, TestSize.Level0)
137+{
138+ OHOS::Security::SAF::SafAgentFence agentFence;
139+ 
140+ std::vector<OHOS::Security::SAF::PermissionQuery> queries;
141+ OHOS::Security::SAF::PermissionQuery query;
142+ OHOS::Security::SAF::RemoteInfo remoteInfo;
143+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLED;
144+ OHOS::Security::SAF::RemoteControlParams remoteControlParams;
145+ remoteControlParams.challenge = "";
146+ remoteInfo.remoteControlParams = remoteControlParams;
147+ query.remoteInfo = remoteInfo;
148+ queries.push_back(query);
149+ 
150+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
151+ int32_t result = agentFence.GenerateControlledDevicePackage(queries, packages);
152+ EXPECT_NE(result, SAF_SUCCESS);
153+}
154+ 
155+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControlledDevicePackageParamsTest001, TestSize.Level0)
156+{
157+ OHOS::Security::SAF::SafAgentFence agentFence;
158+ 
159+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
160+ 
161+ std::vector<bool> verifyRes;
162+ int32_t result = agentFence.VerifyControlledDevicePackage(packages, verifyRes);
163+ EXPECT_NE(result, SAF_SUCCESS);
164+}
165+ 
166+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControlledDevicePackageParamsTest002, TestSize.Level0)
167+{
168+ OHOS::Security::SAF::SafAgentFence agentFence;
169+ 
170+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
171+ for (int i = 0; i < 11; i++) {
172+ OHOS::Security::SAF::RemoteAuthPackage pkg;
173+ packages.push_back(pkg);
174+ }
175+ 
176+ std::vector<bool> verifyRes;
177+ int32_t result = agentFence.VerifyControlledDevicePackage(packages, verifyRes);
178+ EXPECT_NE(result, SAF_SUCCESS);
179+}
180+ 
181+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControlledDevicePackageParamsTest003, TestSize.Level0)
182+{
183+ OHOS::Security::SAF::SafAgentFence agentFence;
184+ 
185+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
186+ OHOS::Security::SAF::RemoteAuthPackage pkg;
187+ pkg.remoteMessage = "";
188+ packages.push_back(pkg);
189+ 
190+ std::vector<bool> verifyRes;
191+ int32_t result = agentFence.VerifyControlledDevicePackage(packages, verifyRes);
192+ EXPECT_NE(result, SAF_SUCCESS);
193+}
194+ 
195+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControlledDevicePackageParamsTest004, TestSize.Level0)
196+{
197+ OHOS::Security::SAF::SafAgentFence agentFence;
198+ 
199+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
200+ OHOS::Security::SAF::RemoteAuthPackage pkg;
201+ pkg.challenge = "invalid_challenge";
202+ packages.push_back(pkg);
203+ 
204+ std::vector<bool> verifyRes;
205+ int32_t result = agentFence.VerifyControlledDevicePackage(packages, verifyRes);
206+ EXPECT_NE(result, SAF_SUCCESS);
207+}
208+ 
209+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest001, TestSize.Level0)
210+{
211+ OHOS::Security::SAF::SafAgentFence agentFence;
212+ 
213+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
214+ 
215+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
216+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
217+ EXPECT_NE(result, SAF_SUCCESS);
218+}
219+ 
220+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest002, TestSize.Level0)
221+{
222+ OHOS::Security::SAF::SafAgentFence agentFence;
223+ 
224+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
225+ for (int i = 0; i < 11; i++) {
226+ OHOS::Security::SAF::RemoteUserAuthResults authResult;
227+ authResults.push_back(authResult);
228+ }
229+ 
230+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
231+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
232+ EXPECT_NE(result, SAF_SUCCESS);
233+}
234+ 
235+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest003, TestSize.Level0)
236+{
237+ OHOS::Security::SAF::SafAgentFence agentFence;
238+ 
239+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
240+ OHOS::Security::SAF::RemoteUserAuthResults authResult;
241+ authResult.results.clear();
242+ authResults.push_back(authResult);
243+ 
244+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
245+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
246+ EXPECT_NE(result, SAF_SUCCESS);
247+}
248+ 
249+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest004, TestSize.Level0)
250+{
251+ OHOS::Security::SAF::SafAgentFence agentFence;
252+ 
253+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
254+ OHOS::Security::SAF::RemoteUserAuthResults authResult;
255+ OHOS::Security::SAF::RemoteUserAuthItem item;
256+ item.permission = "";
257+ authResult.results.push_back(item);
258+ authResults.push_back(authResult);
259+ 
260+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
261+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
262+ EXPECT_NE(result, SAF_SUCCESS);
263+}
264+ 
265+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest005, TestSize.Level0)
266+{
267+ OHOS::Security::SAF::SafAgentFence agentFence;
268+ 
269+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
270+ OHOS::Security::SAF::RemoteUserAuthResults authResult;
271+ OHOS::Security::SAF::RemoteUserAuthItem item;
272+ item.permission = "ohos.permission.TEST";
273+ item.authResult = "";
274+ authResult.results.push_back(item);
275+ authResults.push_back(authResult);
276+ 
277+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
278+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
279+ EXPECT_NE(result, SAF_SUCCESS);
280+}
281+ 
282+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest006, TestSize.Level0)
283+{
284+ OHOS::Security::SAF::SafAgentFence agentFence;
285+ 
286+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
287+ OHOS::Security::SAF::RemoteUserAuthResults authResult;
288+ OHOS::Security::SAF::RemoteUserAuthItem item;
289+ item.permission = "ohos.permission.TEST";
290+ item.authResult = "granted";
291+ authResult.results.push_back(item);
292+ authResult.permissionQuery.remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
293+ authResults.push_back(authResult);
294+ 
295+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
296+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
297+ EXPECT_NE(result, SAF_SUCCESS);
298+}
299+ 
300+HWTEST_F(SafAgentFenceRemoteControlParamsTest, GenerateControllerDevicePackageParamsTest007, TestSize.Level0)
301+{
302+ OHOS::Security::SAF::SafAgentFence agentFence;
303+ 
304+ std::vector<OHOS::Security::SAF::RemoteUserAuthResults> authResults;
305+ OHOS::Security::SAF::RemoteUserAuthResults authResult;
306+ OHOS::Security::SAF::RemoteUserAuthItem item;
307+ item.permission = "ohos.permission.TEST";
308+ item.authResult = "granted";
309+ authResult.results.push_back(item);
310+ authResult.permissionQuery.remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLED;
311+ authResults.push_back(authResult);
312+ 
313+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
314+ int32_t result = agentFence.GenerateControllerDevicePackage(authResults, packages);
315+ EXPECT_NE(result, SAF_SUCCESS);
316+}
317+ 
318+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControllerDevicePackageParamsTest001, TestSize.Level0)
319+{
320+ OHOS::Security::SAF::SafAgentFence agentFence;
321+ 
322+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
323+ OHOS::Security::SAF::RemoteInfo remoteInfo;
324+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
325+ 
326+ std::vector<bool> verifyRes;
327+ int32_t result = agentFence.VerifyControllerDevicePackage(packages, remoteInfo, verifyRes);
328+ EXPECT_NE(result, SAF_SUCCESS);
329+}
330+ 
331+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControllerDevicePackageParamsTest002, TestSize.Level0)
332+{
333+ OHOS::Security::SAF::SafAgentFence agentFence;
334+ 
335+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
336+ for (int i = 0; i < 11; i++) {
337+ OHOS::Security::SAF::RemoteAuthPackage pkg;
338+ packages.push_back(pkg);
339+ }
340+ OHOS::Security::SAF::RemoteInfo remoteInfo;
341+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
342+ 
343+ std::vector<bool> verifyRes;
344+ int32_t result = agentFence.VerifyControllerDevicePackage(packages, remoteInfo, verifyRes);
345+ EXPECT_NE(result, SAF_SUCCESS);
346+}
347+ 
348+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControllerDevicePackageParamsTest003, TestSize.Level0)
349+{
350+ OHOS::Security::SAF::SafAgentFence agentFence;
351+ 
352+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
353+ OHOS::Security::SAF::RemoteAuthPackage pkg;
354+ pkg.remoteMessage = "";
355+ packages.push_back(pkg);
356+ 
357+ OHOS::Security::SAF::RemoteInfo remoteInfo;
358+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
359+ 
360+ std::vector<bool> verifyRes;
361+ int32_t result = agentFence.VerifyControllerDevicePackage(packages, remoteInfo, verifyRes);
362+ EXPECT_NE(result, SAF_SUCCESS);
363+}
364+ 
365+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControllerDevicePackageParamsTest004, TestSize.Level0)
366+{
367+ OHOS::Security::SAF::SafAgentFence agentFence;
368+ 
369+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
370+ OHOS::Security::SAF::RemoteAuthPackage pkg;
371+ pkg.remoteMessage = "valid_message";
372+ packages.push_back(pkg);
373+ 
374+ OHOS::Security::SAF::RemoteInfo remoteInfo;
375+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLED;
376+ 
377+ std::vector<bool> verifyRes;
378+ int32_t result = agentFence.VerifyControllerDevicePackage(packages, remoteInfo, verifyRes);
379+ EXPECT_NE(result, SAF_SUCCESS);
380+}
381+ 
382+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControllerDevicePackageParamsTest005, TestSize.Level0)
383+{
384+ OHOS::Security::SAF::SafAgentFence agentFence;
385+ 
386+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
387+ OHOS::Security::SAF::RemoteAuthPackage pkg;
388+ pkg.remoteMessage = "valid_message";
389+ packages.push_back(pkg);
390+ 
391+ OHOS::Security::SAF::RemoteInfo remoteInfo;
392+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
393+ remoteInfo.remoteId = "";
394+ 
395+ std::vector<bool> verifyRes;
396+ int32_t result = agentFence.VerifyControllerDevicePackage(packages, remoteInfo, verifyRes);
397+ EXPECT_NE(result, SAF_SUCCESS);
398+}
399+ 
400+HWTEST_F(SafAgentFenceRemoteControlParamsTest, VerifyControllerDevicePackageParamsTest006, TestSize.Level0)
401+{
402+ OHOS::Security::SAF::SafAgentFence agentFence;
403+ 
404+ std::vector<OHOS::Security::SAF::RemoteAuthPackage> packages;
405+ OHOS::Security::SAF::RemoteAuthPackage pkg;
406+ pkg.remoteMessage = "valid_message";
407+ packages.push_back(pkg);
408+ 
409+ OHOS::Security::SAF::RemoteInfo remoteInfo;
410+ remoteInfo.role = OHOS::Security::SAF::Role::CONTROLLER;
411+ remoteInfo.remoteId = "test_remote_id";
412+ remoteInfo.remoteControlParams.challenge = "";
413+ 
414+ std::vector<bool> verifyRes;
415+ int32_t result = agentFence.VerifyControllerDevicePackage(packages, remoteInfo, verifyRes);
416+ EXPECT_NE(result, SAF_SUCCESS);
417+}
418+ 
419+}
@@ -0,0 +1,43 @@
1+# Copyright (c) 2026 Huawei Device Co., Ltd.
2+# Licensed under the Apache License, Version 2.0 (the "License");
3+# you may not use this file except in compliance with the License.
4+# You may obtain a copy of the License at
5+#
6+# http://www.apache.org/licenses/LICENSE-2.0
7+#
8+# Unless required by applicable law or agreed to in writing, software
9+# distributed under the License is distributed on an "AS IS" BASIS,
10+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+# See the License for the specific language governing permissions and
12+# limitations under the License.
13+ 
14+import("//base/security/asset/config.gni")
15+import("//build/ohos.gni")
16+import("//build/test.gni")
17+ 
18+module_output_path = "asset/asset"
19+ 
20+ohos_rust_unittest("rust_saf_core_service_ut_test") {
21+ module_out_path = module_output_path
22+ sources = [ "src/lib.rs" ]
23+ deps = [
24+ "../../../../../../services/secure_access_fence/core_service:secure_access_fence_service",
25+ "../../../../../../frameworks/secure_access_fence/definition:saf_definition",
26+ "../../../../../../services/secure_access_fence/common:saf_common",
27+ "../../../../../../frameworks/secure_access_fence/utils:saf_utils",
28+ "../../../../../../interfaces/inner_kits/plugin_interface/secure_access_fence:saf_plugin_interface_rust",
29+ "../../../../../../services/secure_access_fence/plugin:saf_plugin",
30+ "//third_party/rust/crates/lazy-static.rs:lib",
31+ ]
32+ rustflags = []
33+ if (enable_local_test) {
34+ rustflags += [ "--cfg", "feature=\"SAFTest\"" ]
35+ }
36+ external_deps = [
37+ "ipc:ipc_rust",
38+ "hilog:libhilog",
39+ "ylong_runtime:ylong_runtime",
40+ ]
41+ subsystem_name = "security"
42+ part_name = "asset"
43+}
@@ -0,0 +1,28 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! the module test for secure_access_fence core_service
17+mod remote_control;
18+ 
19+#[macro_export]
20+macro_rules! function {
21+ () => {{
22+ fn f() {}
23+ fn type_name_of<T>(_: T) -> &'static str {
24+ std::any::type_name::<T>()
25+ }
26+ type_name_of(f).rsplit("::").find(|&part| part != "f" && part != "{{closure}}").expect("Short function name")
27+ }};
28+}
@@ -0,0 +1,360 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Test cases for generate_controlled_device_package
17+ 
18+use saf_definition::{ErrCode, Role};
19+use secure_access_fence_service::ut_core_service_lib_stub::*;
20+ 
21+#[test]
22+fn test_generate_controlled_device_package_empty() {
23+ let result = generate_controlled_device_package(vec![]);
24+ assert!(result.packages.is_empty());
25+ assert_eq!(ErrCode::Success as i32, result.error_code);
26+}
27+ 
28+#[test]
29+fn test_generate_controlled_device_package_single_valid_query() {
30+ reset_mock_state();
31+ set_mock_sign_success(true);
32+ let query = create_test_permission_query(
33+ Role::Controlled,
34+ "test_domain",
35+ "test_ticket",
36+ vec![create_api_operation("ohos.permission.TEST")],
37+ );
38+ let result = generate_controlled_device_package(vec![query]);
39+ assert_eq!(1, result.packages.len());
40+ assert_eq!(ErrCode::Success as i32, result.error_code);
41+}
42+ 
43+#[test]
44+fn test_generate_controlled_device_package_multiple_valid_queries() {
45+ reset_mock_state();
46+ set_mock_sign_success(true);
47+ let queries = vec![
48+ create_test_permission_query(
49+ Role::Controlled,
50+ "domain1",
51+ "ticket1",
52+ vec![create_api_operation("perm1")],
53+ ),
54+ create_test_permission_query(
55+ Role::Controlled,
56+ "domain2",
57+ "ticket2",
58+ vec![create_cli_operation("cmd", "sub")],
59+ ),
60+ create_test_permission_query(
61+ Role::Controlled,
62+ "domain3",
63+ "ticket3",
64+ vec![create_api_operation("perm2"), create_cli_operation("cmd2", "sub2")],
65+ ),
66+ ];
67+ let result = generate_controlled_device_package(queries);
68+ assert_eq!(3, result.packages.len());
69+ assert_eq!(ErrCode::Success as i32, result.error_code);
70+}
71+ 
72+#[test]
73+fn test_generate_controlled_device_package_invalid_role() {
74+ let query = create_test_permission_query(
75+ Role::Controller,
76+ "test_domain",
77+ "test_ticket",
78+ vec![create_api_operation("ohos.permission.TEST")],
79+ );
80+ let result = generate_controlled_device_package(vec![query]);
81+ assert_eq!(1, result.packages.len());
82+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
83+ assert!(result.packages[0].remote_message.device_info.controller_device_id.is_empty());
84+}
85+ 
86+#[test]
87+fn test_generate_controlled_device_package_empty_domain_id() {
88+ let query = create_test_permission_query(
89+ Role::Controlled,
90+ "",
91+ "test_ticket",
92+ vec![create_api_operation("ohos.permission.TEST")],
93+ );
94+ let result = generate_controlled_device_package(vec![query]);
95+ assert_eq!(1, result.packages.len());
96+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
97+ assert!(result.packages[0].remote_message.device_info.controller_device_id.is_empty());
98+}
99+ 
100+#[test]
101+fn test_generate_controlled_device_package_empty_ticket() {
102+ let query = create_test_permission_query(
103+ Role::Controlled,
104+ "test_domain",
105+ "",
106+ vec![create_api_operation("ohos.permission.TEST")],
107+ );
108+ let result = generate_controlled_device_package(vec![query]);
109+ assert_eq!(1, result.packages.len());
110+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
111+ assert!(result.packages[0].remote_message.device_info.controller_device_id.is_empty());
112+}
113+ 
114+#[test]
115+fn test_generate_controlled_device_package_partial_failure() {
116+ let queries = vec![
117+ create_test_permission_query(
118+ Role::Controlled,
119+ "valid_domain",
120+ "valid_ticket",
121+ vec![create_api_operation("perm")],
122+ ),
123+ create_test_permission_query(
124+ Role::Controller,
125+ "invalid_domain",
126+ "invalid_ticket",
127+ vec![],
128+ ),
129+ ];
130+ let result = generate_controlled_device_package(queries);
131+ assert_eq!(2, result.packages.len());
132+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
133+}
134+ 
135+#[test]
136+fn test_validate_controlled_permission_query_invalid_role_controller() {
137+ let query = create_test_permission_query(
138+ Role::Controller,
139+ "test_domain",
140+ "test_ticket",
141+ vec![],
142+ );
143+ let result = validate_controlled_permission_query_stub(&query);
144+ assert!(result.is_err());
145+ assert_eq!(ErrCode::DataTypeMismatch, result.unwrap_err().code);
146+}
147+ 
148+#[test]
149+fn test_validate_controlled_permission_query_empty_domain_id() {
150+ let query = create_test_permission_query(
151+ Role::Controlled,
152+ "",
153+ "test_ticket",
154+ vec![],
155+ );
156+ let result = validate_controlled_permission_query_stub(&query);
157+ assert!(result.is_err());
158+ assert_eq!(ErrCode::ArgEmpty, result.unwrap_err().code);
159+}
160+ 
161+#[test]
162+fn test_validate_controlled_permission_query_empty_ticket() {
163+ let query = create_test_permission_query(
164+ Role::Controlled,
165+ "test_domain",
166+ "",
167+ vec![],
168+ );
169+ let result = validate_controlled_permission_query_stub(&query);
170+ assert!(result.is_err());
171+ assert_eq!(ErrCode::ArgEmpty, result.unwrap_err().code);
172+}
173+ 
174+#[test]
175+fn test_validate_controlled_permission_query_valid() {
176+ let query = create_test_permission_query(
177+ Role::Controlled,
178+ "test_domain",
179+ "test_ticket",
180+ vec![create_api_operation("test_permission")],
181+ );
182+ let result = validate_controlled_permission_query_stub(&query);
183+ assert!(result.is_ok());
184+}
185+ 
186+#[test]
187+fn test_parse_cli_and_permission_cli_operation() {
188+ let operations = vec![create_cli_operation("cmd", "sub")];
189+ let result = parse_cli_and_permission_stub(&operations);
190+ assert!(result.is_ok());
191+ let (cli_infos, api_permissions) = result.unwrap();
192+ assert_eq!(1, cli_infos.len());
193+ assert!(api_permissions.is_empty());
194+ assert_eq!("cmd", cli_infos[0].cmd_name);
195+ assert_eq!("sub", cli_infos[0].sub_cmd);
196+}
197+ 
198+#[test]
199+fn test_parse_cli_and_permission_api_operation() {
200+ let operations = vec![create_api_operation("ohos.permission.TEST")];
201+ let result = parse_cli_and_permission_stub(&operations);
202+ assert!(result.is_ok());
203+ let (cli_infos, api_permissions) = result.unwrap();
204+ assert!(cli_infos.is_empty());
205+ assert_eq!(1, api_permissions.len());
206+ assert_eq!("ohos.permission.TEST", api_permissions[0]);
207+}
208+ 
209+#[test]
210+fn test_parse_cli_and_permission_mixed_operations() {
211+ let operations = vec![
212+ create_cli_operation("cmd1", "sub1"),
213+ create_api_operation("perm1"),
214+ create_cli_operation("cmd2", "sub2"),
215+ create_api_operation("perm2"),
216+ ];
217+ let result = parse_cli_and_permission_stub(&operations);
218+ assert!(result.is_ok());
219+ let (cli_infos, api_permissions) = result.unwrap();
220+ assert_eq!(2, cli_infos.len());
221+ assert_eq!(2, api_permissions.len());
222+}
223+ 
224+#[test]
225+fn test_parse_cli_and_permission_empty_cli_cmd_name() {
226+ let operations = vec![create_cli_operation("", "sub")];
227+ let result = parse_cli_and_permission_stub(&operations);
228+ assert!(result.is_err());
229+ assert_eq!(ErrCode::ArgEmpty, result.unwrap_err().code);
230+}
231+ 
232+#[test]
233+fn test_parse_cli_and_permission_empty_api_permission() {
234+ let operations = vec![create_api_operation("")];
235+ let result = parse_cli_and_permission_stub(&operations);
236+ assert!(result.is_err());
237+ assert_eq!(ErrCode::ArgEmpty, result.unwrap_err().code);
238+}
239+ 
240+#[test]
241+fn test_create_empty_package() {
242+ let package = create_empty_package_stub();
243+ assert!(package.remote_message.device_info.controller_device_id.is_empty());
244+ assert!(package.remote_message.device_info.controlled_device_id.is_empty());
245+ assert!(package.remote_message.remote_auth_message.is_empty());
246+ assert!(package.remote_message.caller_bundle_name.is_empty());
247+ assert!(package.challenge.is_empty());
248+ assert!(package.ticket.is_empty());
249+}
250+ 
251+#[test]
252+fn test_build_remote_auth_message_basic() {
253+ let query = create_test_permission_query(
254+ Role::Controlled,
255+ "test_domain",
256+ "test_ticket",
257+ vec![create_api_operation("test_permission")],
258+ );
259+ let cli_infos = vec![];
260+ let api_permissions = vec!["test_permission".to_string()];
261+
262+ let result = build_remote_auth_message_stub(&query, cli_infos, api_permissions, 1234567890);
263+ assert!(result.is_ok());
264+
265+ let json = result.unwrap();
266+ assert!(json.contains("1234567890"));
267+ assert!(json.contains("test_permission"));
268+ assert!(json.contains("test_domain"));
269+}
270+ 
271+#[test]
272+fn test_build_remote_auth_message_with_cli() {
273+ let query = create_test_permission_query(
274+ Role::Controlled,
275+ "test_domain",
276+ "test_ticket",
277+ vec![
278+ create_cli_operation("test_cmd", "test_sub"),
279+ create_api_operation("test_permission"),
280+ ],
281+ );
282+ let cli_infos = vec![saf_definition::CommandInfo {
283+ cmd_name: "test_cmd".to_string(),
284+ sub_cmd: "test_sub".to_string(),
285+ }];
286+ let api_permissions = vec!["test_permission".to_string()];
287+
288+ let result = build_remote_auth_message_stub(&query, cli_infos, api_permissions, 1234567890);
289+ assert!(result.is_ok());
290+
291+ let json = result.unwrap();
292+ assert!(json.contains("test_cmd"));
293+ assert!(json.contains("test_sub"));
294+}
295+ 
296+// ==================== Mock tests ====================
297+ 
298+#[test]
299+fn test_generate_controlled_device_package_sign_success() {
300+ reset_mock_state();
301+ set_mock_sign_success(true);
302+ set_mock_local_udid("test_udid");
303+
304+ let query = create_test_permission_query(
305+ Role::Controlled,
306+ "test_domain",
307+ "test_ticket",
308+ vec![create_api_operation("ohos.permission.TEST")],
309+ );
310+ let result = generate_controlled_device_package(vec![query]);
311+
312+ assert_eq!(1, result.packages.len());
313+ assert_eq!(ErrCode::Success as i32, result.error_code);
314+ assert!(!result.packages[0].ticket.is_empty());
315+ assert_eq!("mock_controller_udid", result.packages[0].remote_message.device_info.controller_device_id);
316+}
317+ 
318+#[test]
319+fn test_generate_controlled_device_package_sign_failure() {
320+ reset_mock_state();
321+ set_mock_sign_success(false);
322+ set_mock_local_udid("mock_local_udid");
323+
324+ let query = create_test_permission_query(
325+ Role::Controlled,
326+ "test_domain",
327+ "test_ticket",
328+ vec![create_api_operation("ohos.permission.TEST")],
329+ );
330+ let result = generate_controlled_device_package(vec![query]);
331+
332+ assert_eq!(1, result.packages.len());
333+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
334+ assert!(result.packages[0].ticket.is_empty());
335+}
336+ 
337+#[test]
338+fn test_generate_controlled_device_package_partial_sign_failure() {
339+ reset_mock_state();
340+ set_mock_sign_success(true);
341+ set_mock_local_udid("mock_local_udid");
342+
343+ let valid_query = create_test_permission_query(
344+ Role::Controlled,
345+ "valid_domain",
346+ "valid_ticket",
347+ vec![create_api_operation("perm")],
348+ );
349+ let invalid_query = create_test_permission_query(
350+ Role::Controlled,
351+ "",
352+ "test_ticket",
353+ vec![create_api_operation("perm")],
354+ );
355+
356+ let result = generate_controlled_device_package(vec![valid_query, invalid_query]);
357+
358+ assert_eq!(2, result.packages.len());
359+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
360+}
@@ -0,0 +1,311 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Test cases for generate_controller_device_package
17+ 
18+use saf_definition::{ErrCode, Role, RemoteUserAuthItem, RemoteControlParams, RemoteInfo, PermissionQuery};
19+use secure_access_fence_service::ut_core_service_lib_stub::*;
20+ 
21+#[test]
22+fn test_generate_controller_device_package_empty() {
23+ let result = generate_controller_device_package(vec![]);
24+ assert!(result.packages.is_empty());
25+ assert_eq!(ErrCode::Success as i32, result.error_code);
26+}
27+ 
28+#[test]
29+fn test_generate_controller_device_package_single_valid() {
30+ let auth_result = create_test_remote_user_auth_results("ohos.permission.TEST", "granted");
31+ let result = generate_controller_device_package(vec![auth_result]);
32+ assert_eq!(1, result.packages.len());
33+ assert_eq!(ErrCode::Success as i32, result.error_code);
34+}
35+ 
36+#[test]
37+fn test_generate_controller_device_package_multiple_valid() {
38+ let auth_results = vec![
39+ create_test_remote_user_auth_results("perm1", "granted"),
40+ create_test_remote_user_auth_results("perm2", "denied"),
41+ create_test_remote_user_auth_results("perm3", "granted"),
42+ ];
43+ let result = generate_controller_device_package(auth_results);
44+ assert_eq!(3, result.packages.len());
45+ assert_eq!(ErrCode::Success as i32, result.error_code);
46+}
47+ 
48+#[test]
49+fn test_generate_controller_device_package_invalid_role() {
50+ let auth_result = saf_definition::RemoteUserAuthResults {
51+ results: vec![RemoteUserAuthItem {
52+ permission: "perm".to_string(),
53+ auth_result: "granted".to_string(),
54+ }],
55+ permission_query: PermissionQuery {
56+ operation_info: vec![],
57+ need_ticket: true,
58+ ticket_expire_time_ms: 3600000,
59+ caller_token_id: 0,
60+ domain_id: "test_domain".to_string(),
61+ remote_info: RemoteInfo {
62+ role: Role::Controlled,
63+ remote_id: "test_remote_id".to_string(),
64+ domain_id: "test_domain".to_string(),
65+ remote_control_params: RemoteControlParams {
66+ challenge: "1234567890".to_string(),
67+ remote_control_ticket: "test_ticket".to_string(),
68+ controlled_device_name: "controlled".to_string(),
69+ controller_device_name: "controller".to_string(),
70+ sign_verify_msg: String::new(),
71+ },
72+ },
73+ },
74+ };
75+ let result = generate_controller_device_package(vec![auth_result]);
76+ assert_eq!(1, result.packages.len());
77+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
78+}
79+ 
80+#[test]
81+fn test_generate_controller_device_package_empty_challenge() {
82+ let auth_result = saf_definition::RemoteUserAuthResults {
83+ results: vec![RemoteUserAuthItem {
84+ permission: "perm".to_string(),
85+ auth_result: "granted".to_string(),
86+ }],
87+ permission_query: PermissionQuery {
88+ operation_info: vec![],
89+ need_ticket: true,
90+ ticket_expire_time_ms: 3600000,
91+ caller_token_id: 0,
92+ domain_id: "test_domain".to_string(),
93+ remote_info: RemoteInfo {
94+ role: Role::Controller,
95+ remote_id: "test_remote_id".to_string(),
96+ domain_id: "test_domain".to_string(),
97+ remote_control_params: RemoteControlParams {
98+ challenge: "".to_string(),
99+ remote_control_ticket: "test_ticket".to_string(),
100+ controlled_device_name: "controlled".to_string(),
101+ controller_device_name: "controller".to_string(),
102+ sign_verify_msg: String::new(),
103+ },
104+ },
105+ },
106+ };
107+ let result = generate_controller_device_package(vec![auth_result]);
108+ assert_eq!(1, result.packages.len());
109+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
110+}
111+ 
112+#[test]
113+fn test_generate_controller_device_package_invalid_challenge_format() {
114+ let auth_result = saf_definition::RemoteUserAuthResults {
115+ results: vec![RemoteUserAuthItem {
116+ permission: "perm".to_string(),
117+ auth_result: "granted".to_string(),
118+ }],
119+ permission_query: PermissionQuery {
120+ operation_info: vec![],
121+ need_ticket: true,
122+ ticket_expire_time_ms: 3600000,
123+ caller_token_id: 0,
124+ domain_id: "test_domain".to_string(),
125+ remote_info: RemoteInfo {
126+ role: Role::Controller,
127+ remote_id: "test_remote_id".to_string(),
128+ domain_id: "test_domain".to_string(),
129+ remote_control_params: RemoteControlParams {
130+ challenge: "invalid_challenge".to_string(),
131+ remote_control_ticket: "test_ticket".to_string(),
132+ controlled_device_name: "controlled".to_string(),
133+ controller_device_name: "controller".to_string(),
134+ sign_verify_msg: String::new(),
135+ },
136+ },
137+ },
138+ };
139+ let result = generate_controller_device_package(vec![auth_result]);
140+ assert_eq!(1, result.packages.len());
141+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
142+}
143+ 
144+#[test]
145+fn test_generate_controller_device_package_partial_failure() {
146+ let valid_auth = create_test_remote_user_auth_results("perm1", "granted");
147+ let invalid_auth = saf_definition::RemoteUserAuthResults {
148+ results: vec![RemoteUserAuthItem {
149+ permission: "perm2".to_string(),
150+ auth_result: "granted".to_string(),
151+ }],
152+ permission_query: PermissionQuery {
153+ operation_info: vec![],
154+ need_ticket: true,
155+ ticket_expire_time_ms: 3600000,
156+ caller_token_id: 0,
157+ domain_id: "test_domain".to_string(),
158+ remote_info: RemoteInfo {
159+ role: Role::Controlled,
160+ remote_id: "test_remote_id".to_string(),
161+ domain_id: "test_domain".to_string(),
162+ remote_control_params: RemoteControlParams {
163+ challenge: "1234567890".to_string(),
164+ remote_control_ticket: "test_ticket".to_string(),
165+ controlled_device_name: "controlled".to_string(),
166+ controller_device_name: "controller".to_string(),
167+ sign_verify_msg: String::new(),
168+ },
169+ },
170+ },
171+ };
172+ let result = generate_controller_device_package(vec![valid_auth, invalid_auth]);
173+ assert_eq!(2, result.packages.len());
174+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
175+}
176+ 
177+#[test]
178+fn test_validate_controller_permission_query_valid() {
179+ let query = PermissionQuery {
180+ operation_info: vec![],
181+ need_ticket: true,
182+ ticket_expire_time_ms: 3600000,
183+ caller_token_id: 0,
184+ domain_id: "test_domain".to_string(),
185+ remote_info: RemoteInfo {
186+ role: Role::Controller,
187+ remote_id: "test_remote_id".to_string(),
188+ domain_id: "test_domain".to_string(),
189+ remote_control_params: RemoteControlParams {
190+ challenge: "1234567890".to_string(),
191+ remote_control_ticket: "test_ticket".to_string(),
192+ controlled_device_name: "controlled".to_string(),
193+ controller_device_name: "controller".to_string(),
194+ sign_verify_msg: String::new(),
195+ },
196+ },
197+ };
198+ let result = validate_controller_permission_query_stub(&query);
199+ assert!(result.is_ok());
200+}
201+ 
202+#[test]
203+fn test_validate_controller_permission_query_invalid_role() {
204+ let query = PermissionQuery {
205+ operation_info: vec![],
206+ need_ticket: true,
207+ ticket_expire_time_ms: 3600000,
208+ caller_token_id: 0,
209+ domain_id: "test_domain".to_string(),
210+ remote_info: RemoteInfo {
211+ role: Role::Controlled,
212+ remote_id: "test_remote_id".to_string(),
213+ domain_id: "test_domain".to_string(),
214+ remote_control_params: RemoteControlParams {
215+ challenge: "1234567890".to_string(),
216+ remote_control_ticket: "test_ticket".to_string(),
217+ controlled_device_name: "controlled".to_string(),
218+ controller_device_name: "controller".to_string(),
219+ sign_verify_msg: String::new(),
220+ },
221+ },
222+ };
223+ let result = validate_controller_permission_query_stub(&query);
224+ assert!(result.is_err());
225+ assert_eq!(ErrCode::DataTypeMismatch, result.unwrap_err().code);
226+}
227+ 
228+#[test]
229+fn test_build_auth_results_json_single() {
230+ let results = vec![RemoteUserAuthItem {
231+ permission: "perm1".to_string(),
232+ auth_result: "granted".to_string(),
233+ }];
234+ let result = build_auth_results_json_stub(&results);
235+ assert!(result.is_ok());
236+ let json = result.unwrap();
237+ assert!(json.contains("perm1"));
238+ assert!(json.contains("granted"));
239+}
240+ 
241+#[test]
242+fn test_build_auth_results_json_multiple() {
243+ let results = vec![
244+ RemoteUserAuthItem {
245+ permission: "perm1".to_string(),
246+ auth_result: "granted".to_string(),
247+ },
248+ RemoteUserAuthItem {
249+ permission: "perm2".to_string(),
250+ auth_result: "denied".to_string(),
251+ },
252+ ];
253+ let result = build_auth_results_json_stub(&results);
254+ assert!(result.is_ok());
255+ let json = result.unwrap();
256+ assert!(json.contains("perm1"));
257+ assert!(json.contains("perm2"));
258+}
259+ 
260+#[test]
261+fn test_build_auth_results_json_empty() {
262+ let results: Vec<RemoteUserAuthItem> = vec![];
263+ let result = build_auth_results_json_stub(&results);
264+ assert!(result.is_ok());
265+}
266+ 
267+// ==================== Mock tests ====================
268+ 
269+#[test]
270+fn test_generate_controller_device_package_sign_success() {
271+ reset_mock_state();
272+ set_mock_sign_success(true);
273+ set_mock_local_udid("test_udid");
274+
275+ let auth_result = create_test_remote_user_auth_results("ohos.permission.TEST", "granted");
276+ let result = generate_controller_device_package(vec![auth_result]);
277+
278+ assert_eq!(1, result.packages.len());
279+ assert_eq!(ErrCode::Success as i32, result.error_code);
280+ assert!(!result.packages[0].ticket.is_empty());
281+}
282+ 
283+#[test]
284+fn test_generate_controller_device_package_sign_failure() {
285+ reset_mock_state();
286+ set_mock_sign_success(false);
287+ set_mock_local_udid("mock_local_udid");
288+
289+ let auth_result = create_test_remote_user_auth_results("ohos.permission.TEST", "granted");
290+ let result = generate_controller_device_package(vec![auth_result]);
291+
292+ assert_eq!(1, result.packages.len());
293+ assert_eq!(ErrCode::GeneralError as i32, result.error_code);
294+ assert!(result.packages[0].ticket.is_empty());
295+}
296+ 
297+#[test]
298+fn test_generate_controller_device_package_multiple_with_mock() {
299+ reset_mock_state();
300+ set_mock_sign_success(true);
301+ set_mock_local_udid("mock_local_udid");
302+
303+ let auth_results = vec![
304+ create_test_remote_user_auth_results("perm1", "granted"),
305+ create_test_remote_user_auth_results("perm2", "denied"),
306+ ];
307+ let result = generate_controller_device_package(auth_results);
308+
309+ assert_eq!(2, result.packages.len());
310+ assert_eq!(ErrCode::Success as i32, result.error_code);
311+}
@@ -0,0 +1,19 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! the module test for remote_control
17+mod generate_controlled_device_package_test;
18+mod generate_controller_device_package_test;
19+mod verify_device_package_test;
@@ -0,0 +1,281 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+//! Test cases for verify_controlled_device_package and verify_controller_device_package
17+ 
18+use saf_definition::{Role, ErrCode};
19+use secure_access_fence_service::ut_core_service_lib_stub::*;
20+ 
21+// ==================== verify_controlled_device_package tests ====================
22+ 
23+#[test]
24+fn test_verify_controlled_device_package_empty() {
25+ let result = verify_controlled_device_package(100, vec![]);
26+ assert!(result.results.is_empty());
27+ assert_eq!(result.error_code, ErrCode::InvalidArrayLen as i32);
28+}
29+ 
30+#[test]
31+fn test_check_controller_device_id_match_true() {
32+ let package = create_test_remote_auth_package("test_udid", "controlled_udid", "1234567890");
33+ assert!(check_controller_device_id_match_stub("test_udid", &package));
34+}
35+ 
36+#[test]
37+fn test_check_controller_device_id_match_false() {
38+ let package = create_test_remote_auth_package("other_udid", "controlled_udid", "1234567890");
39+ assert!(!check_controller_device_id_match_stub("test_udid", &package));
40+}
41+ 
42+#[test]
43+fn test_check_controller_device_id_match_empty() {
44+ let package = create_test_remote_auth_package("", "controlled_udid", "1234567890");
45+ assert!(!check_controller_device_id_match_stub("test_udid", &package));
46+}
47+ 
48+#[test]
49+fn test_parse_challenge_valid() {
50+ assert_eq!(Some(1234567890u64), parse_challenge_stub("1234567890"));
51+}
52+ 
53+#[test]
54+fn test_parse_challenge_valid_large() {
55+ assert_eq!(Some(18446744073709551615u64), parse_challenge_stub("18446744073709551615"));
56+}
57+ 
58+#[test]
59+fn test_parse_challenge_invalid_format() {
60+ assert_eq!(None, parse_challenge_stub("invalid"));
61+}
62+ 
63+#[test]
64+fn test_parse_challenge_empty() {
65+ assert_eq!(None, parse_challenge_stub(""));
66+}
67+ 
68+#[test]
69+fn test_parse_challenge_negative() {
70+ assert_eq!(None, parse_challenge_stub("-123"));
71+}
72+ 
73+#[test]
74+fn test_validate_ticket_expiration_not_expired() {
75+ assert!(validate_ticket_expiration_stub(1000, 3600000, 2000));
76+}
77+ 
78+#[test]
79+fn test_validate_ticket_expiration_exactly_not_expired() {
80+ assert!(validate_ticket_expiration_stub(1000, 1000, 2000));
81+}
82+ 
83+#[test]
84+fn test_validate_ticket_expiration_expired() {
85+ assert!(!validate_ticket_expiration_stub(1000, 100, 2000));
86+}
87+ 
88+#[test]
89+fn test_validate_ticket_expiration_expired_exactly() {
90+ assert!(!validate_ticket_expiration_stub(1000, 999, 2000));
91+}
92+ 
93+#[test]
94+fn test_validate_ticket_expiration_zero_expire_time() {
95+ assert!(validate_ticket_expiration_stub(1000, 0, 500));
96+}
97+ 
98+// ==================== verify_controller_device_package tests ====================
99+ 
100+#[test]
101+fn test_verify_controller_device_package_empty() {
102+ let remote_info = create_test_remote_info(Role::Controller, "test_remote_id");
103+ let result = verify_controller_device_package(100, vec![], &remote_info);
104+ assert!(result.results.is_empty());
105+ assert_eq!(result.error_code, ErrCode::InvalidArrayLen as i32);
106+}
107+ 
108+#[test]
109+fn test_check_role_is_controller_true() {
110+ assert!(check_role_is_controller_stub(Role::Controller));
111+}
112+ 
113+#[test]
114+fn test_check_role_is_controller_false() {
115+ assert!(!check_role_is_controller_stub(Role::Controlled));
116+}
117+ 
118+// ==================== Mock tests ====================
119+ 
120+#[test]
121+fn test_verify_controlled_device_package_with_mock() {
122+ reset_mock_state();
123+ set_mock_local_udid("mock_controller_udid");
124+ set_mock_verify_success(true);
125+
126+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
127+ let result = verify_controlled_device_package(100, vec![package]);
128+
129+ assert_eq!(result.results.len(), 1);
130+ assert!(result.results[0]);
131+ assert_eq!(result.error_code, ErrCode::Success as i32);
132+}
133+ 
134+#[test]
135+fn test_verify_controlled_device_package_udid_mismatch_with_mock() {
136+ reset_mock_state();
137+ set_mock_local_udid("different_udid");
138+ set_mock_verify_success(true);
139+
140+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
141+ let result = verify_controlled_device_package(100, vec![package]);
142+
143+ assert_eq!(result.results.len(), 1);
144+ assert!(!result.results[0]);
145+ assert_eq!(result.error_code, ErrCode::Success as i32);
146+}
147+ 
148+#[test]
149+fn test_verify_controller_device_package_with_mock() {
150+ reset_mock_state();
151+ set_mock_local_udid("mock_local_udid");
152+ set_mock_verify_success(true);
153+
154+ let remote_info = create_test_remote_info(Role::Controller, "mock_controller_udid");
155+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
156+ let result = verify_controller_device_package(100, vec![package], &remote_info);
157+
158+ assert_eq!(result.results.len(), 1);
159+ assert!(result.results[0]);
160+ assert_eq!(result.error_code, ErrCode::Success as i32);
161+}
162+ 
163+#[test]
164+fn test_verify_controller_device_package_role_mismatch() {
165+ let remote_info = create_test_remote_info(Role::Controlled, "mock_controller_udid");
166+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
167+ let result = verify_controller_device_package(100, vec![package], &remote_info);
168+
169+ assert!(result.results.is_empty());
170+ assert_eq!(result.error_code, ErrCode::DataTypeMismatch as i32);
171+}
172+ 
173+#[test]
174+fn test_verify_controller_device_package_verify_failure() {
175+ reset_mock_state();
176+ set_mock_local_udid("mock_local_udid");
177+ set_mock_verify_success(false);
178+
179+ let remote_info = create_test_remote_info(Role::Controller, "mock_controller_udid");
180+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
181+ let result = verify_controller_device_package(100, vec![package], &remote_info);
182+
183+ assert_eq!(result.results.len(), 1);
184+ assert!(!result.results[0]);
185+ assert_eq!(result.error_code, ErrCode::Success as i32);
186+}
187+ 
188+// ==================== Exception tests ====================
189+ 
190+#[test]
191+fn test_verify_controlled_device_package_invalid_challenge() {
192+ reset_mock_state();
193+ set_mock_local_udid("mock_controller_udid");
194+ set_mock_verify_success(true);
195+
196+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "invalid_challenge");
197+ let result = verify_controlled_device_package(100, vec![package]);
198+
199+ assert_eq!(result.results.len(), 1);
200+ assert!(!result.results[0]);
201+ assert_eq!(result.error_code, ErrCode::Success as i32);
202+}
203+ 
204+#[test]
205+fn test_verify_controlled_device_package_empty_challenge() {
206+ reset_mock_state();
207+ set_mock_local_udid("mock_controller_udid");
208+ set_mock_verify_success(true);
209+
210+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "");
211+ let result = verify_controlled_device_package(100, vec![package]);
212+
213+ assert_eq!(result.results.len(), 1);
214+ assert!(!result.results[0]);
215+ assert_eq!(result.error_code, ErrCode::Success as i32);
216+}
217+ 
218+#[test]
219+fn test_verify_controlled_device_package_multiple_packages_partial_failure() {
220+ reset_mock_state();
221+ set_mock_local_udid("mock_controller_udid");
222+ set_mock_verify_success(true);
223+
224+ let valid_package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
225+ let invalid_package = create_test_remote_auth_package("different_udid", "controlled_udid", "1234567890");
226+
227+ let result = verify_controlled_device_package(100, vec![valid_package, invalid_package]);
228+
229+ assert_eq!(result.results.len(), 2);
230+ assert!(result.results[0]);
231+ assert!(!result.results[1]);
232+ assert_eq!(result.error_code, ErrCode::Success as i32);
233+}
234+ 
235+#[test]
236+fn test_verify_controller_device_package_invalid_challenge() {
237+ reset_mock_state();
238+ set_mock_local_udid("mock_local_udid");
239+ set_mock_verify_success(true);
240+
241+ let remote_info = create_test_remote_info(Role::Controller, "mock_controller_udid");
242+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "invalid_challenge");
243+ let result = verify_controller_device_package(100, vec![package], &remote_info);
244+
245+ assert_eq!(result.results.len(), 1);
246+ assert!(!result.results[0]);
247+ assert_eq!(result.error_code, ErrCode::Success as i32);
248+}
249+ 
250+#[test]
251+fn test_verify_controller_device_package_empty_challenge() {
252+ reset_mock_state();
253+ set_mock_local_udid("mock_local_udid");
254+ set_mock_verify_success(true);
255+
256+ let remote_info = create_test_remote_info(Role::Controller, "mock_controller_udid");
257+ let package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "");
258+ let result = verify_controller_device_package(100, vec![package], &remote_info);
259+
260+ assert_eq!(result.results.len(), 1);
261+ assert!(!result.results[0]);
262+ assert_eq!(result.error_code, ErrCode::Success as i32);
263+}
264+ 
265+#[test]
266+fn test_verify_controller_device_package_multiple_packages_partial_failure() {
267+ reset_mock_state();
268+ set_mock_local_udid("mock_local_udid");
269+ set_mock_verify_success(true);
270+
271+ let remote_info = create_test_remote_info(Role::Controller, "mock_controller_udid");
272+ let valid_package = create_test_remote_auth_package("mock_controller_udid", "controlled_udid", "1234567890");
273+ let invalid_package = create_test_remote_auth_package("different_id", "controlled_udid", "1234567890");
274+
275+ let result = verify_controller_device_package(100, vec![valid_package, invalid_package], &remote_info);
276+
277+ assert_eq!(result.results.len(), 2);
278+ assert!(result.results[0]);
279+ assert!(!result.results[1]);
280+ assert_eq!(result.error_code, ErrCode::Success as i32);
281+}
@@ -0,0 +1,179 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+/// the module test stub for controlled_device
17+ 
18+use saf_definition::{
19+ CommandInfo, OperationInfo, OperationType, PermissionQuery, RemoteInfo,
20+ RemoteControlParams, Role,
21+};
22+use crate::remote_control::controlled_device::*;
23+ 
24+pub use crate::remote_control::{
25+ BatchGenerateResult,
26+ BatchVerifyResult,
27+ generate_controlled_device_package,
28+ verify_controlled_device_package,
29+};
30+ 
31+/// validate_controlled_permission_query stub
32+#[allow(dead_code)]
33+pub fn validate_controlled_permission_query_stub(query: &PermissionQuery) -> saf_definition::Result<()> {
34+ validate_controlled_permission_query(query)
35+}
36+ 
37+/// parse_cli_and_permission stub
38+#[allow(dead_code)]
39+pub fn parse_cli_and_permission_stub(
40+ operation_info: &[OperationInfo]
41+) -> saf_definition::Result<(Vec<CommandInfo>, Vec<String>)> {
42+ parse_cli_and_permission(operation_info)
43+}
44+ 
45+/// build_remote_auth_message stub
46+#[allow(dead_code)]
47+pub fn build_remote_auth_message_stub(
48+ query: &PermissionQuery,
49+ cli_infos: Vec<CommandInfo>,
50+ api_permissions: Vec<String>,
51+ challenge: u64,
52+) -> saf_definition::Result<String> {
53+ build_remote_auth_message(query, cli_infos, api_permissions, challenge)
54+}
55+ 
56+/// create_empty_package stub
57+#[allow(dead_code)]
58+pub fn create_empty_package_stub() -> saf_definition::RemoteAuthPackage {
59+ crate::remote_control::create_empty_package()
60+}
61+ 
62+/// check_controller_device_id_match stub
63+#[allow(dead_code)]
64+pub fn check_controller_device_id_match_stub(local_udid: &str, package: &saf_definition::RemoteAuthPackage) -> bool {
65+ check_controller_device_id_match(local_udid, package)
66+}
67+ 
68+/// parse_challenge stub
69+#[allow(dead_code)]
70+pub fn parse_challenge_stub(challenge_str: &str) -> Option<u64> {
71+ crate::remote_control::parse_challenge(challenge_str)
72+}
73+ 
74+/// validate_ticket_expiration stub
75+#[allow(dead_code)]
76+pub fn validate_ticket_expiration_stub(challenge: u64, expire_time_ms: u64, current_time: u64) -> bool {
77+ match challenge.checked_add(expire_time_ms) {
78+ Some(expire_time) => expire_time >= current_time,
79+ None => false,
80+ }
81+}
82+ 
83+/// create_test_remote_auth_package
84+pub fn create_test_remote_auth_package(controller_id: &str, controlled_id: &str, challenge: &str) -> saf_definition::RemoteAuthPackage {
85+ let expire_time_ms: u64 = 86400000; // 24h
86+ let now_ms: u64 = std::time::SystemTime::now()
87+ .duration_since(std::time::UNIX_EPOCH)
88+ .unwrap()
89+ .as_millis() as u64;
90+ let challenge_value = match challenge.parse::<u64>() {
91+ Ok(_) => now_ms.to_string(),
92+ Err(_) => challenge.to_string(),
93+ };
94+ saf_definition::RemoteAuthPackage {
95+ remote_message: saf_definition::RemoteMessage {
96+ device_info: saf_definition::DeviceIdHeader {
97+ controller_device_id: controller_id.to_string(),
98+ controlled_device_id: controlled_id.to_string(),
99+ },
100+ remote_auth_message: format!("{{\"ticketExpireTimeMs\":\"{}\"}}", expire_time_ms),
101+ caller_bundle_name: String::new(),
102+ },
103+ challenge: challenge_value,
104+ ticket: String::new(),
105+ }
106+}
107+ 
108+/// create_test_permission_query
109+pub fn create_test_permission_query(
110+ role: Role,
111+ domain_id: &str,
112+ remote_control_ticket: &str,
113+ operation_info: Vec<OperationInfo>,
114+) -> PermissionQuery {
115+ PermissionQuery {
116+ operation_info,
117+ need_ticket: true,
118+ ticket_expire_time_ms: 3600000,
119+ caller_token_id: 0,
120+ domain_id: domain_id.to_string(),
121+ remote_info: RemoteInfo {
122+ role,
123+ remote_id: "test_remote_id".to_string(),
124+ domain_id: domain_id.to_string(),
125+ remote_control_params: RemoteControlParams {
126+ challenge: "1234567890".to_string(),
127+ remote_control_ticket: remote_control_ticket.to_string(),
128+ controlled_device_name: "controlled_device".to_string(),
129+ controller_device_name: "controller_device".to_string(),
130+ sign_verify_msg: "sign_verify_msg".to_string(),
131+ },
132+ },
133+ }
134+}
135+ 
136+/// create_cli_operation
137+pub fn create_cli_operation(cmd_name: &str, sub_cmd: &str) -> OperationInfo {
138+ OperationInfo {
139+ operation_type: OperationType::Cli,
140+ cli_cmd_info: CommandInfo {
141+ cmd_name: cmd_name.to_string(),
142+ sub_cmd: sub_cmd.to_string(),
143+ },
144+ permission: String::new(),
145+ }
146+}
147+ 
148+/// create_api_operation
149+pub fn create_api_operation(permission: &str) -> OperationInfo {
150+ OperationInfo {
151+ operation_type: OperationType::Api,
152+ cli_cmd_info: CommandInfo {
153+ cmd_name: String::new(),
154+ sub_cmd: String::new(),
155+ },
156+ permission: permission.to_string(),
157+ }
158+}
159+ 
160+/// set_mock_local_udid
161+pub fn set_mock_local_udid(udid: &str) {
162+ saf_common::set_mock_local_udid(udid);
163+}
164+ 
165+/// set_mock_sign_success
166+pub fn set_mock_sign_success(success: bool) {
167+ crate::remote_control::account_based_auth_manager::set_mock_sign_success(success);
168+}
169+ 
170+/// set_mock_verify_success
171+pub fn set_mock_verify_success(success: bool) {
172+ crate::remote_control::account_based_auth_manager::set_mock_verify_success(success);
173+}
174+ 
175+/// reset all mock state to defaults
176+pub fn reset_mock_state() {
177+ crate::remote_control::account_based_auth_manager::reset_mock_state();
178+ saf_common::reset_mock_local_udid();
179+}
@@ -0,0 +1,93 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+/// the module test stub for controller_device
17+ 
18+use saf_definition::{Role, RemoteUserAuthItem, PermissionQuery};
19+use crate::remote_control::controller_device::*;
20+ 
21+pub use crate::remote_control::{
22+ BatchGenerateResult,
23+ BatchVerifyResult,
24+ generate_controller_device_package,
25+ verify_controller_device_package,
26+};
27+ 
28+/// validate_controller_permission_query stub
29+#[allow(dead_code)]
30+pub fn validate_controller_permission_query_stub(query: &PermissionQuery) -> saf_definition::Result<()> {
31+ validate_controller_permission_query(query)
32+}
33+ 
34+/// build_auth_results_json stub
35+#[allow(dead_code)]
36+pub fn build_auth_results_json_stub(results: &[RemoteUserAuthItem]) -> saf_definition::Result<String> {
37+ build_auth_results_json(results)
38+}
39+ 
40+/// build_controller_remote_auth_message stub
41+#[allow(dead_code)]
42+pub fn build_controller_remote_auth_message_stub(
43+ query: &PermissionQuery,
44+ auth_results_json: &str,
45+ challenge: u64,
46+ local_device_id: &str,
47+ permissions: &[String],
48+) -> saf_definition::Result<String> {
49+ build_controller_remote_auth_message(query, auth_results_json, challenge, local_device_id, permissions)
50+}
51+ 
52+/// check_role_is_controller stub
53+#[allow(dead_code)]
54+pub fn check_role_is_controller_stub(role: saf_definition::Role) -> bool {
55+ check_role_is_controller(role)
56+}
57+ 
58+use crate::remote_control::controlled_device::ut_controlled_device_stub::{create_test_permission_query, create_api_operation};
59+ 
60+/// create_test_remote_user_auth_results
61+pub fn create_test_remote_user_auth_results(
62+ permission: &str,
63+ auth_result: &str,
64+) -> saf_definition::RemoteUserAuthResults {
65+ saf_definition::RemoteUserAuthResults {
66+ results: vec![RemoteUserAuthItem {
67+ permission: permission.to_string(),
68+ auth_result: auth_result.to_string(),
69+ }],
70+ permission_query: create_test_permission_query(
71+ Role::Controller,
72+ "test_domain",
73+ "test_ticket",
74+ vec![create_api_operation(permission)],
75+ ),
76+ }
77+}
78+ 
79+/// create_test_remote_info
80+pub fn create_test_remote_info(role: saf_definition::Role, remote_id: &str) -> saf_definition::RemoteInfo {
81+ saf_definition::RemoteInfo {
82+ role,
83+ remote_id: remote_id.to_string(),
84+ domain_id: "test_domain".to_string(),
85+ remote_control_params: saf_definition::RemoteControlParams {
86+ challenge: "1234567890".to_string(),
87+ remote_control_ticket: "test_ticket".to_string(),
88+ controlled_device_name: "controlled".to_string(),
89+ controller_device_name: "controller".to_string(),
90+ sign_verify_msg: String::new(),
91+ },
92+ }
93+}
@@ -0,0 +1,18 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+/// the module test stub for secure_access_fence core_service
17+pub use crate::remote_control::ut_controlled_device_stub::*;
18+pub use crate::remote_control::ut_controller_device_stub::*;