/* * Copyright (c) 2021 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #define IOKIT_ENABLE_SHARED_PTR #include #include #include #include #include #include #include #include #include #include #include /* Pulled from CoreAnalyticsHub.h in CoreAnalytics project */ static const char *kCoreAnalyticsMatchingClassName = "CoreAnalyticsHub"; core_analytics_hub_functions_t *core_analytics_hub_functions = NULL; core_analytics_family_service_t * core_analytics_family_match() { OSSharedPtr dict = IOService::serviceMatching(kCoreAnalyticsMatchingClassName); OSSharedPtr service = nullptr; if (dict) { service = IOService::waitForMatchingService(dict.get()); } else { os_log_with_startup_serial(OS_LOG_DEFAULT, "No service matching %s", kCoreAnalyticsMatchingClassName); } if (service) { return service.detach(); } else { os_log_with_startup_serial(OS_LOG_DEFAULT, "Unable to match CoreAnalyticsHub"); return nullptr; } } void core_analytics_family_release(core_analytics_family_service_t *service) { service->release(); } typedef struct core_analytics_serialized_event_s { OSSharedPtr case_event_name; OSSharedPtr case_event; } core_analytics_serialized_event_t; /* * Stringifying CA_BOOL is different in C vs. C++ b/c the underlying * bool typename is different (_Bool vs bool). * We want to support both since the event may have been defined in C or C++ code. */ extern const char *core_analytics_ca_bool_c_stringified; const char *core_analytics_ca_bool_cpp_stringified = _CA_STRINGIFY_EXPAND(CA_BOOL); static OSSharedPtr serialize_event_field(const char **field_spec, ptrdiff_t *event, OSSharedPtr *key) { OSSharedPtr field = nullptr; ptrdiff_t field_value = (ptrdiff_t) *event; uint64_t number_value; const char *str_value; size_t str_len = 0; bool bool_value; if (strcmp(*field_spec, _CA_STRINGIFY_EXPAND(CA_INT)) == 0) { number_value = *(const uint64_t *)field_value; field = OSNumber::withNumber(number_value, sizeof(uint64_t) * 8); *event += sizeof(const uint64_t); } else if (strcmp(*field_spec, core_analytics_ca_bool_cpp_stringified) == 0 || strcmp(*field_spec, core_analytics_ca_bool_c_stringified) == 0) { bool_value = *(const bool *)field_value; field = OSBoolean::withBoolean(bool_value); *event += sizeof(const bool); } else if ((str_len = core_analytics_field_is_string(*field_spec)) != 0) { str_value = (const char *) field_value; assert(strlen(str_value) < str_len); if (strlen(str_value) < str_len) { field = OSString::withCString(str_value); } else { field = nullptr; } *event += str_len; } else { panic("Unknown CoreAnalytics event type: %s.", *field_spec); } *field_spec += strlen(*field_spec) + 1; *key = OSSymbol::withCString(*field_spec); assert(*key != nullptr); if (!*key) { return nullptr; } *field_spec += strlen(*field_spec) + 1; return field; } static int core_analytics_serialize_event(const char *event_spec, const ca_event_t event, core_analytics_serialized_event_t *serialized_event) { serialized_event->case_event_name = nullptr; serialized_event->case_event = nullptr; OSSharedPtr dict = nullptr; bool success; OSSharedPtr name = OSString::withCStringNoCopy(event_spec); if (!name) { return ENOMEM; } dict = OSDictionary::withCapacity(1); if (!dict) { return ENOMEM; } const char *spec_curr = event_spec + strlen(event_spec) + 1; ptrdiff_t event_curr = (ptrdiff_t) event->data; while (strlen(spec_curr) != 0) { OSSharedPtr key = NULL; OSSharedPtr object = serialize_event_field(&spec_curr, &event_curr, &key); if (!object) { return ENOMEM; } success = dict->setObject(key, object); if (!success) { return ENOMEM; } } serialized_event->case_event_name = name; serialized_event->case_event = dict; return 0; } int core_analytics_send_event_lazy( core_analytics_family_service_t *core_analytics_hub, const char *event_spec, const ca_event_t event) { int ret = 0; bool success; static constexpr size_t kMaxRetries = 5; static constexpr uint64_t kDelayBetweenRetries = 1000; // microseconds core_analytics_serialized_event_t serialized; if (!core_analytics_hub_functions) { return EAGAIN; } ret = core_analytics_serialize_event(event_spec, event, &serialized); if (ret != 0) { return ret; } for (size_t i = 0; i < kMaxRetries; i++) { success = core_analytics_hub_functions->analytics_send_event_lazy(core_analytics_hub, serialized.case_event_name.get(), serialized.case_event.get()); if (success) { break; } else { if (i != kMaxRetries - 1) { delay(kDelayBetweenRetries); } else { ret = EAGAIN; break; } } } return ret; } void core_analytics_hub_register(core_analytics_hub_functions_t *fns) { if (fns->version != CORE_ANALYTICS_FUNCTIONS_TABLE_VERSION) { panic("CoreAnalyticsHub is out of sync with xnu. CoreAnalyticsHub table version: %d. xnu table version: %d", fns->version, CORE_ANALYTICS_FUNCTIONS_TABLE_VERSION); } core_analytics_hub_functions = fns; os_log_with_startup_serial(OS_LOG_DEFAULT, "Registered CoreAnalyticsHub functions with xnu."); }