"""SQLite parser plugin for iOS Health (healthdb_secure.sqlite) database."""
from plaso.containers import events
from plaso.parsers import sqlite
from plaso.parsers.sqlite_plugins import interface
[docs]
class IOSHealthAchievementsEventData(events.EventData):
"""iOS Health achievement event data.
Attributes:
creation_time (dfdatetime.DateTimeValues): data and time of the creation of the
achievement.
creator_device (int): identifier of the device that created the achievement.
earned_date (str): Date the achievement was earned.
synchronization_provenance (int): synchronization provenance.
template_unique_name (str): Unique name of the achievement template.
value_canonical_unit (str): Unit of the value (e.g., "count").
value_in_canonical_unit (float): Value of the achievement in canonical units.
"""
DATA_TYPE = "ios:health:achievement"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.creation_time = None
self.creator_device = None
self.earned_date = None
self.synchronization_provenance = None
self.template_unique_name = None
self.value_canonical_unit = None
self.value_in_canonical_unit = None
[docs]
class IOSHealthAllWatchSleepEventData(events.EventData):
"""iOS Health all watch sleep event data.
Attributes:
duration (float): duration in seconds.
end_time (dfdatetime.DateTimeValues): date and time the sleep ended.
sleep_state_code (int): sleep state code.
start_time (dfdatetime.DateTimeValues): date and time the sleep started.
"""
DATA_TYPE = "ios:health:all_watch_sleep"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.duration = None
self.end_time = None
self.sleep_state_code = None
self.start_time = None
[docs]
class IOSHealthHeadphoneAudioEventData(events.EventData):
"""iOS Health Headphone Audio Levels event data.
Attributes:
bundle_identifier (str): application bundle identifier.
decibels (float): sound level from quantity_samples.quantity.
device_local_identifier, (str): device local identifier.
device_manufacturer (str): source_devices.manufacturer.
device_model (str): source_devices.model.
device_name (str): source_devices.name.
end_time (dfdatetime.DateTimeValues): Cocoa time end date.
start_time (dfdatetime.DateTimeValues): Cocoa time start date.
"""
DATA_TYPE = "ios:health:headphone_audio_levels"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.bundle_identifier = None
self.decibels = None
self.device_local_identifier = None
self.device_manufacturer = None
self.device_model = None
self.device_name = None
self.end_time = None
self.start_time = None
[docs]
class IOSHealthHeartRateEventData(events.EventData):
"""iOS Health heart rate (sample) event data.
Attributes:
added_time (dfdatetime.DateTimeValues): data and time the sample was added to the
database.
bpm (int): beats per minute.
context (float): context.
device_name (str): source device name.
end_time (dfdatetime.DateTimeValues): date and time the sample ended.
hardware (str): source device hardware.
manufacturer (str): source device manufacturer.
series_count (int): quantity_sample_series.count.
series_key (str): quantity_sample_series.hfd_key.
software_version (str): software version.
source_name (str): source name.
source_options (str): source options.
start_time (dfdatetime.DateTimeValues): date and time the sample started.
time_zone (str): time zone.
"""
DATA_TYPE = "ios:health:heart_rate"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.added_time = None
self.bpm = None
self.context = None
self.device_name = None
self.end_time = None
self.hardware = None
self.manufacturer = None
self.series_count = None
self.series_key = None
self.software_version = None
self.source_name = None
self.source_options = None
self.start_time = None
self.time_zone = None
[docs]
class IOSHealthHeightEventData(events.EventData):
"""iOS Health height (sample) event data.
Attributes:
height (float): height in meters.
start_time (dfdatetime.DateTimeValues): date and time the sample started.
"""
DATA_TYPE = "ios:health:height"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.height = None
self.start_time = None
# TODO: merge with IOSHealthHeartRateEventData
[docs]
class IOSHealthRestingHeartRateEventData(events.EventData):
"""iOS Health resting heart rate (sample) event data.
Attributes:
added_time (dfdatetime.DateTimeValues): date and time the sample was added to the
database.
end_time (dfdatetime.DateTimeValues): date and time the sample ended.
hardware (str): device hardware.
resting_heart_rate (int): resting heart rate in beats per minute (BPM).
source_name (str): source name.
start_time (dfdatetime.DateTimeValues): date and time the sample started.
"""
DATA_TYPE = "ios:health:resting_heart_rate"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.added_time = None
self.end_time = None
self.hardware = None
self.resting_heart_rate = None
self.source_name = None
self.start_time = None
[docs]
class IOSHealthSourceDevicesEventData(events.EventData):
"""iOS Health Source Devices event data.
Attributes:
added_time (dfdatetime.DateTimeValues): date and time the device was added to the
database.
device_name (str): name of the device.
firmware (str): firmware version of the device.
hardware (str): hardware version of the device.
local_identifier (str): local identifier.
manufacturer (str): manufacturer of the device.
model (str): model of the device.
software (str): software version of the device.
synchronization_identity (int): synchronization identifier.
synchronization_provenance (int): synchronization provenance.
"""
DATA_TYPE = "ios:health:source_devices"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.added_time = None
self.device_name = None
self.firmware = None
self.hardware = None
self.local_identifier = None
self.manufacturer = None
self.model = None
self.software = None
self.synchronization_identity = None
self.synchronization_provenance = None
[docs]
class IOSHealthStepsEventData(events.EventData):
"""iOS Health steps (sample) event data.
Attributes:
device (str): device used to record the data (e.g. Apple Watch).
duration (float): duration in seconds.
end_time (dfdatetime.DateTimeValues): date and time the sample ended.
number_of_steps (float): total number of steps.
start_time (dfdatetime.DateTimeValues): date and time the sample started.
"""
DATA_TYPE = "ios:health:steps"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.device = None
self.duration = None
self.end_time = None
self.number_of_steps = None
self.start_time = None
[docs]
class IOSHealthWeightEventData(events.EventData):
"""iOS Health weight (sample) event data.
Attributes:
start_time (dfdatetime.DateTimeValues): date and time the sample started.
weight (float): Weight in kilograms.
"""
DATA_TYPE = "ios:health:weight"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.start_time = None
self.weight = None
[docs]
class IOSHealthWorkoutsEventData(events.EventData):
"""iOS Health workouts event data.
Attributes:
activity_type (str): workout activity type.
duration (float): duration in seconds.
end_time (dfdatetime.DateTimeValues): date and time the sample ended.
goal (str): workout goal value.
goal_type (str): workout goal type.
start_time (dfdatetime.DateTimeValues): date and time the sample started.
total_basal_energy_burned (float): basal energy (kcal).
total_distance (float): total distance in kilometers.
total_energy_burned (float): active energy (kcal).
total_flights_climbed (float): flights climbed total.
total_weekly_steps (float): steps total.
"""
DATA_TYPE = "ios:health:workouts"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.activity_type = None
self.duration = None
self.end_time = None
self.goal = None
self.goal_type = None
self.start_time = None
self.total_basal_energy_burned = None
self.total_distance = None
self.total_energy_burned = None
self.total_flights_climbed = None
self.total_weekly_steps = None
[docs]
class IOSHealthWristTemperatureEventData(events.EventData):
"""iOS Health wrist temperature event data.
Attributes:
added_time (dfdatetime.DateTimeValues): data and time the sample was added to the
database.
algorithm_version (float): algorithm version.
device_hardware (str): device hardware.
device_manufacturer (str): device manufacturer.
device_model (str): device model.
device_name (str): device name.
end_time (dfdatetime.DateTimeValues): date and time the sample ended.
software_version (str): software version.
source_name (str): source name.
start_time (dfdatetime.DateTimeValues): date and time the sample started.
surface_temperature (float): skin surface temperature in Celsius (°C).
wrist_temperature (float): wrist temperature in Celsius (°C).
"""
DATA_TYPE = "ios:health:wrist_temperature"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.added_time = None
self.algorithm_version = None
self.device_hardware = None
self.device_manufacturer = None
self.device_model = None
self.device_name = None
self.end_time = None
self.software_version = None
self.source_name = None
self.start_time = None
self.surface_temperature = None
self.wrist_temperature = None
[docs]
class IOSHealthPlugin(interface.SQLitePlugin):
"""SQLite parser plugin for iOS Health ((healthdb_secure.sqlite) database."""
NAME = "ios_health"
DATA_FORMAT = "iOS Health SQLite database (healthdb_secure.sqlite) file"
_OBJECT_VALUE_QUERY = (
"SELECT metadata_values.value_type AS value_type, "
"metadata_values.string_value AS string_value, "
"metadata_values.numerical_value AS numerical_value, "
"metadata_values.date_value AS date_value, "
"metadata_values.data_value AS data_value, "
"metadata_keys.key as key "
"FROM metadata_values JOIN metadata_keys "
"ON metadata_keys.ROWID = metadata_values.key_id "
"WHERE metadata_values.object_id = {0:d}"
)
REQUIRED_STRUCTURE = {
"ACHAchievementsPlugin_earned_instances": frozenset(
[
"created_date",
"creator_device",
"earned_date",
"sync_provenance",
"template_unique_name",
"value_canonical_unit",
"value_in_canonical_unit",
]
),
"data_provenances": frozenset(
["ROWID", "device_id", "source_id", "source_version", "tz_name"]
),
"metadata_keys": frozenset(["ROWID", "key"]),
"metadata_values": frozenset(["key_id", "numerical_value", "object_id"]),
"objects": frozenset(["creation_date", "data_id", "provenance", "type"]),
"quantity_samples": frozenset(["data_id", "quantity"]),
"samples": frozenset(["data_id", "data_type", "end_date", "start_date"]),
"source_devices": frozenset(
[
"ROWID",
"creation_date",
"firmware",
"hardware",
"localIdentifier",
"manufacturer",
"model",
"name",
"software",
"sync_provenance",
]
),
"sources": frozenset(["ROWID", "name", "product_type", "source_options"]),
"workouts": frozenset(
[
"activity_type",
"duration",
"goal",
"goal_type",
"total_basal_energy_burned",
"total_distance",
"total_energy_burned",
"total_flights_climbed",
"total_w_steps",
]
),
}
QUERIES = [
(
(
"SELECT template_unique_name, created_date, earned_date, "
"value_in_canonical_unit, value_canonical_unit, creator_device, "
"sync_provenance FROM ACHAchievementsPlugin_earned_instances"
),
"_ParseAchievementRow",
),
(
(
"SELECT "
"data_provenances.origin_product_type AS origin_product_type, "
"data_provenances.source_version AS software_version, "
"data_provenances.tz_name AS tz_name, "
"category_samples.value AS category_value, "
"objects.creation_date AS creation_date, "
"objects.type AS object_type, "
"quantity_samples.quantity as quantity, "
"samples.data_id AS sample_data_identifier, "
"samples.data_type AS sample_type, "
"samples.end_date AS end_date, "
"samples.start_date AS start_date, "
"source_devices.hardware AS device_hardware, "
"source_devices.localIdentifier AS device_local_identifier, "
"source_devices.manufacturer AS device_manufacturer, "
"source_devices.model AS device_model, "
"source_devices.name AS device_name, "
"sources.name AS source_name, "
"sources.product_type AS source_product_type, "
"sources.source_options AS source_options "
"FROM samples "
"LEFT JOIN category_samples "
"ON samples.data_id = category_samples.data_id "
"LEFT JOIN objects ON objects.data_id = samples.data_id "
"LEFT JOIN quantity_samples "
"ON quantity_samples.data_id = samples.data_id "
"LEFT JOIN data_provenances "
"ON data_provenances.ROWID = objects.provenance "
"LEFT JOIN source_devices "
"ON source_devices.ROWID = data_provenances.device_id "
"LEFT JOIN sources ON sources.ROWID = data_provenances.source_id"
),
"_ParseSamplesRow",
),
(
(
"SELECT creation_date, firmware, hardware, localIdentifier, "
"manufacturer, model, name, software, sync_provenance "
"FROM source_devices"
),
"_ParseSourceDevicesRow",
),
(
(
"SELECT samples.start_date AS start_date, "
"samples.end_date AS end_date, "
"workouts.activity_type AS activity_type, "
"workouts.duration AS duration, "
"workouts.total_distance AS total_distance, "
"workouts.total_energy_burned AS total_energy_burned, "
"workouts.total_basal_energy_burned AS total_basal_energy_burned, "
"workouts.goal_type AS goal_type, workouts.goal AS goal, "
"workouts.total_flights_climbed AS total_flights_climbed, "
"workouts.total_w_steps AS total_w_steps "
"FROM workouts LEFT JOIN samples ON samples.data_id = workouts.data_id"
),
"_ParseWorkoutsRow",
),
]
SCHEMAS = [
{
"ACHAchievementsPlugin_earned_instances": (
"CREATE TABLE ACHAchievementsPlugin_earned_instances (ROWID INTEGER "
"PRIMARY KEY AUTOINCREMENT, template_unique_name TEXT, created_date "
"REAL, earned_date TEXT, value_in_canonical_unit REAL, "
"value_canonical_unit TEXT, creator_device INTEGER, sync_provenance "
"INTEGER)"
),
"ACHAchievementsPlugin_templates": (
"CREATE TABLE ACHAchievementsPlugin_templates (ROWID INTEGER "
"PRIMARY KEY AUTOINCREMENT, unique_name TEXT UNIQUE NOT NULL, "
"version INTEGER, minimum_engine_version INTEGER, created_date "
"REAL, creator_device INTEGER, source_name TEXT, predicate TEXT, "
"value_expression TEXT, progress_expression TEXT, goal_expression "
"TEXT, triggers INTEGER, earn_limit INTEGER, visibility_predicate "
"TEXT, visibility_start_date TEXT, visibility_end_date TEXT, "
"availability_predicate TEXT, availability_start_date TEXT, "
"availability_end_date TEXT, available_country_codes TEXT, "
"alertability_predicate TEXT, alert_dates TEXT, "
"duplicateremoval_strategy INTEGER, duplicateremoval_calendar_unit "
"INTEGER, earn_date INTEGER, display_order INTEGER, "
"displays_earned_instance_count INTEGER, canonical_unit TEXT, "
"sync_provenance INTEGER, available_suffixes TEXT, grace_predicate "
"TEXT, grace_visibility_predicate TEXT, grace_value_expression "
"TEXT, grace_progress_expression TEXT, grace_goal_expression TEXT)"
),
"account_owner_samples": (
"CREATE TABLE account_owner_samples (data_id INTEGER PRIMARY KEY, "
"name TEXT NOT NULL, birth_date BLOB)"
),
"activity_caches": (
"CREATE TABLE activity_caches (data_id INTEGER PRIMARY KEY "
"REFERENCES samples (data_id) ON DELETE CASCADE, cache_index "
"INTEGER, sequence INTEGER NOT NULL, activity_mode INTEGER, "
"wheelchair_use INTEGER, energy_burned REAL, energy_burned_goal "
"REAL, energy_burned_goal_date REAL, move_minutes REAL, "
"move_minutes_goal REAL, move_minutes_goal_date REAL, brisk_minutes "
"REAL, brisk_minutes_goal REAL, brisk_minutes_goal_date REAL, "
"active_hours REAL, active_hours_goal REAL, active_hours_goal_date "
"REAL, steps REAL, pushes REAL, walk_distance REAL, "
"deep_breathing_duration REAL, flights INTEGER, energy_burned_stats "
"BLOB, move_minutes_stats BLOB, brisk_minutes_stats BLOB)"
),
"activitysharing_competition_lists": (
"CREATE TABLE activitysharing_competition_lists (data_id INTEGER "
"PRIMARY KEY, friend_uuid BLOB, type INTEGER, system_fields BLOB, "
"owner INTEGER, UNIQUE(friend_uuid, type))"
),
"activitysharing_competitions": (
"CREATE TABLE activitysharing_competitions (data_id INTEGER PRIMARY "
"KEY, friend_uuid BLOB, competition_uuid BLOB, competition_type "
"INTEGER, current_cache_index INTEGER, last_pushed_cache_index "
"INTEGER, scores BLOB, opponent_scores BLOB, start_date_components "
"BLOB, duration_date_components BLOB, "
"preferred_victory_badge_styles BLOB, maximum_points_per_day "
"INTEGER, UNIQUE(friend_uuid, competition_uuid))"
),
"allergy_record_samples": (
"CREATE TABLE allergy_record_samples (data_id INTEGER PRIMARY KEY, "
"allergy_codings BLOB NOT NULL, onset_date BLOB, asserter TEXT, "
"reactions BLOB, criticality_coding BLOB, last_occurence_date BLOB, "
"recorded_date BLOB, status_coding BLOB)"
),
"binary_samples": (
"CREATE TABLE binary_samples (data_id INTEGER PRIMARY KEY "
"REFERENCES samples (data_id) ON DELETE CASCADE, payload BLOB)"
),
"category_samples": (
"CREATE TABLE category_samples (data_id INTEGER PRIMARY KEY, value "
"INTEGER)"
),
"cda_documents": (
"CREATE TABLE cda_documents (data_id INTEGER PRIMARY KEY REFERENCES "
"samples (data_id) ON DELETE CASCADE, document_data BLOB, title "
"TEXT NOT NULL, patient_name TEXT NOT NULL, author_name TEXT NOT "
"NULL, custodian_name TEXT NOT NULL)"
),
"clinical_accounts": (
"CREATE TABLE clinical_accounts (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, identifier BLOB NOT NULL UNIQUE, user_enabled_flag "
"INTEGER NOT NULL, relogin_needed_flag INTEGER NOT NULL, "
"creation_date REAL NOT NULL, last_fetch_date REAL, "
"last_full_fetch_date REAL, last_extracted_rowid INTEGER, "
"last_submitted_rowid INTEGER, last_extracted_rules_version "
"INTEGER, patient_hash TEXT, credential_id INTEGER REFERENCES "
"clinical_credentials (ROWID) ON DELETE SET NULL, gateway_id "
"INTEGER NOT NULL UNIQUE REFERENCES clinical_gateways (ROWID) ON "
"DELETE NO ACTION, sync_identifier BLOB NOT NULL UNIQUE, "
"modification_date REAL NOT NULL, sync_anchor INTEGER NOT NULL "
"UNIQUE, sync_provenance INTEGER NOT NULL)"
),
"clinical_authorization_sessions": (
"CREATE TABLE clinical_authorization_sessions (ROWID INTEGER "
"PRIMARY KEY AUTOINCREMENT, query TEXT NOT NULL, state BLOB NOT "
"NULL UNIQUE, code TEXT, creation_date REAL NOT NULL, gateway_id "
"INTEGER REFERENCES clinical_gateways (ROWID) ON DELETE CASCADE, "
"account_id INTEGER REFERENCES clinical_accounts (ROWID) ON DELETE "
"CASCADE, CHECK((account_id IS NULL AND gateway_id IS NOT NULL) OR "
"(account_id IS NOT NULL AND gateway_id IS NULL)))"
),
"clinical_credentials": (
"CREATE TABLE clinical_credentials (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, identifier BLOB NOT NULL, expiration_date REAL, "
"scope TEXT, patient_id TEXT, creation_date REAL NOT NULL)"
),
"clinical_deleted_accounts": (
"CREATE TABLE clinical_deleted_accounts (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, sync_identifier BLOB NOT NULL, deletion_date REAL "
"NOT NULL, sync_provenance INTEGER NOT NULL, "
"UNIQUE(sync_identifier))"
),
"clinical_gateways": (
"CREATE TABLE clinical_gateways (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, external_id TEXT NOT NULL, last_reported_status "
"INTEGER NOT NULL, revision INTEGER NOT NULL, raw_content BLOB NOT "
"NULL, sync_anchor INTEGER NOT NULL UNIQUE, sync_provenance INTEGER "
"NOT NULL, UNIQUE(external_id))"
),
"clinical_record_samples": (
"CREATE TABLE clinical_record_samples (data_id INTEGER PRIMARY KEY, "
"display_name TEXT NOT NULL, fhir_resource_resource_type TEXT, "
"fhir_resource_identifier TEXT, fhir_resource_data BLOB, "
"fhir_resource_source_url TEXT, fhir_resource_last_updated_date "
"REAL)"
),
"concept_index": (
"CREATE TABLE concept_index (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, sample_uuid BLOB NOT NULL, concept_identifier "
"INTEGER NOT NULL, version INTEGER NOT NULL, key_path TEXT NOT "
"NULL, compound_index INTEGER, type INTEGER, ontology_version "
"INTEGER NOT NULL DEFAULT 0)"
),
"condition_record_samples": (
"CREATE TABLE condition_record_samples (data_id INTEGER PRIMARY "
"KEY, condition_codings BLOB NOT NULL, category_codings BLOB NOT "
"NULL, asserter TEXT, abatement BLOB, onset BLOB, recorded_date "
"BLOB, clinical_status_coding BLOB, verification_status_coding "
"BLOB, severity_codings BLOB, body_sites_codings BLOB)"
),
"correlations": (
"CREATE TABLE correlations (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, correlation INTEGER, object INTEGER, provenance "
"INTEGER, UNIQUE(correlation, object))"
),
"data_provenances": (
"CREATE TABLE data_provenances (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, sync_provenance INTEGER NOT NULL, "
"origin_product_type TEXT NOT NULL, origin_build TEXT NOT NULL, "
"local_product_type TEXT NOT NULL, local_build TEXT NOT NULL, "
"source_id INTEGER NOT NULL, device_id INTEGER NOT NULL, "
"source_version TEXT NOT NULL, tz_name TEXT NOT NULL, "
"origin_major_version INTEGER NOT NULL, origin_minor_version "
"INTEGER NOT NULL, origin_patch_version INTEGER NOT NULL, "
"derived_flags INTEGER NOT NULL, UNIQUE(sync_provenance, "
"origin_product_type, origin_build, local_product_type, "
"local_build, source_id, device_id, source_version, tz_name, "
"origin_major_version, origin_minor_version, origin_patch_version))"
),
"data_series": (
"CREATE TABLE data_series (data_id INTEGER PRIMARY KEY REFERENCES "
"samples (data_id) ON DELETE CASCADE, frozen INTEGER NOT NULL "
"DEFAULT 0, count INTEGER NOT NULL DEFAULT 0, insertion_era INTEGER "
"NOT NULL DEFAULT 0, hfd_key INTEGER UNIQUE NOT NULL)"
),
"devices": (
"CREATE TABLE devices (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, "
"device_uuid BLOB, device_name TEXT, device_service INTEGER, "
"device_last_connect REAL, device_enabled BOOLEAN default 1, "
"UNIQUE(device_uuid, device_service))"
),
"diagnostic_test_report_samples": (
"CREATE TABLE diagnostic_test_report_samples (data_id INTEGER "
"PRIMARY KEY, diagnostic_test_codings BLOB, panel_name TEXT NOT "
"NULL, results BLOB, effective_start_date BLOB, status_coding BLOB "
"NOT NULL, effective_end_date BLOB, issue_date BLOB NOT NULL)"
),
"diagnostic_test_result_samples": (
"CREATE TABLE diagnostic_test_result_samples (data_id INTEGER "
"PRIMARY KEY, diagnostic_test_codings BLOB NOT NULL, value BLOB, "
"reference_ranges BLOB, effective_start_date BLOB, category TEXT "
"NOT NULL, issue_date BLOB, effective_end_date BLOB, status_coding "
"BLOB NOT NULL, interpretation_codings BLOB, comments TEXT, "
"body_site_codings BLOB, method_codings BLOB, performers BLOB)"
),
"external_sync_ids": (
"CREATE TABLE external_sync_ids (object_id INTEGER PRIMARY KEY "
"REFERENCES objects (data_id) ON DELETE CASCADE, source_id INTEGER "
"NOT NULL, object_code INTEGER NOT NULL, sid TEXT NOT NULL, version "
"REAL NOT NULL, deleted INTEGER NON NULL)"
),
"fitness_friend_achievements": (
"CREATE TABLE fitness_friend_achievements (data_id INTEGER PRIMARY "
"KEY, friend_uuid BLOB, template_unique_name TEXT, completed_date "
"REAL, value)"
),
"fitness_friend_activity_snapshots": (
"CREATE TABLE fitness_friend_activity_snapshots (data_id INTEGER "
"PRIMARY KEY, friend_uuid BLOB, active_hours REAL, "
"active_hours_goal REAL, brisk_minutes REAL, brisk_minutes_goal "
"REAL, energy_burned REAL, energy_burned_goal REAL, move_minutes "
"REAL, move_minutes_goal REAL, activity_move_mode INTEGER, steps "
"REAL, walk_run_distance REAL, snapshot_index INTEGER, source_uuid "
"BLOB, uploaded_date REAL, vulcan_count REAL, vulcan_condition "
"INTEGER, timezone_offset INTEGER, UNIQUE(friend_uuid, "
"snapshot_index, source_uuid))"
),
"fitness_friend_workouts": (
"CREATE TABLE fitness_friend_workouts (data_id INTEGER PRIMARY KEY, "
"friend_uuid BLOB, duration REAL, total_energy_burned REAL, "
"total_basal_energy_burned REAL, total_distance REAL, activity_type "
"INTEGER, goal_type INTEGER, goal REAL, bundle_id TEXT, "
"is_watch_workout INTEGER, is_indoor_workout INTEGER, "
"device_manufacturer TEXT, device_model TEXT, activity_move_mode "
"INTEGER DEFAULT 0)"
),
"key_value_secure": (
"CREATE TABLE key_value_secure (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, category INTEGER NOT NULL, domain TEXT NOT NULL, "
"key TEXT NOT NULL, value, provenance INTEGER NOT NULL, mod_date "
"REAL NOT NULL, UNIQUE(category, domain, key))"
),
"medical_records": (
"CREATE TABLE medical_records (data_id INTEGER PRIMARY KEY, note "
"TEXT, entered_in_error INTEGER NON NULL, modified_date REAL, "
"fhir_identifier TEXT NON NULL, locale TEXT, extraction_version "
"INTEGER NON NULL, sort_date REAL NON NULL, sort_date_key_path TEXT "
"NON NULL)"
),
"medication_dispense_record_samples": (
"CREATE TABLE medication_dispense_record_samples (data_id INTEGER "
"PRIMARY KEY, medication_codings BLOB NOT NULL, quantity_dispensed "
"BLOB, preparation_date BLOB, hand_over_date BLOB, dosages BLOB, "
"earliest_dosage_date BLOB, status_coding BLOB, "
"days_supply_quantity BLOB)"
),
"medication_order_samples": (
"CREATE TABLE medication_order_samples (data_id INTEGER PRIMARY "
"KEY, medication_codings BLOB NOT NULL, prescriber TEXT, "
"number_of_fills INTEGER NOT NULL, dosages BLOB, "
"earliest_dosage_date BLOB, written_date BLOB, ended_date BLOB, "
"status_coding BLOB NOT NULL, reason_codings BLOB, "
"reason_ended_codings BLOB)"
),
"medication_record_samples": (
"CREATE TABLE medication_record_samples (data_id INTEGER PRIMARY "
"KEY, medication_codings BLOB NOT NULL, assertion_type INTEGER NOT "
"NULL, asserter TEXT, assertion_date BLOB, status_coding BLOB NOT "
"NULL, dosages BLOB, earliest_dosage_date BLOB, "
"reason_for_use_codings BLOB, not_taken INTEGER NOT NULL, "
"reasons_not_taken_codings BLOB, effective_start_date BLOB, "
"effective_end_date BLOB)"
),
"metadata_keys": (
"CREATE TABLE metadata_keys (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, key TEXT UNIQUE)"
),
"metadata_values": (
"CREATE TABLE metadata_values (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, key_id INTEGER, object_id INTEGER, value_type "
"INTEGER NOT NULL DEFAULT 0, string_value TEXT, numerical_value "
"REAL, date_value REAL, data_value BLOB)"
),
"object_authorizations": (
"CREATE TABLE object_authorizations (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, object BLOB NOT NULL REFERENCES objects (uuid) ON "
"DELETE CASCADE, source BLOB NOT NULL, status INTEGER NOT NULL, "
"sync_provenance INTEGER NOT NULL, modification_date REAL NOT NULL, "
"UNIQUE(object, source, sync_provenance))"
),
"objects": (
"CREATE TABLE objects (data_id INTEGER PRIMARY KEY AUTOINCREMENT, "
"uuid BLOB UNIQUE, provenance INTEGER NOT NULL REFERENCES "
"data_provenances (ROWID) ON DELETE CASCADE, type INTEGER, "
"creation_date REAL)"
),
"original_fhir_resources": (
"CREATE TABLE original_fhir_resources (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, type TEXT NOT NULL, account_id INTEGER NOT NULL "
"REFERENCES clinical_accounts (ROWID) ON DELETE CASCADE, id TEXT "
"NOT NULL, sync_provenance INTEGER NOT NULL, raw_content BLOB NOT "
"NULL, received_date REAL NOT NULL, received_date_timezone TEXT NOT "
"NULL, fhir_version TEXT NOT NULL, source_url TEXT, "
"extraction_hints INTEGER, origin_major_version INTEGER NOT NULL, "
"origin_minor_version INTEGER NOT NULL, origin_patch_version "
"INTEGER NOT NULL, origin_build TEXT NOT NULL, UNIQUE(type, "
"account_id, id))"
),
"original_fhir_resources_last_seen": (
"CREATE TABLE original_fhir_resources_last_seen (ROWID INTEGER "
"PRIMARY KEY AUTOINCREMENT, resource_id INTEGER NOT NULL REFERENCES "
"original_fhir_resources (ROWID) ON DELETE CASCADE, last_seen_date "
"REAL NOT NULL)"
),
"pending_associations": (
"CREATE TABLE pending_associations (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, parent_uuid BLOB NOT NULL, child_uuid BLOB NOT "
"NULL, provenance INTEGER NOT NULL, UNIQUE(parent_uuid, "
"child_uuid))"
),
"procedure_record_samples": (
"CREATE TABLE procedure_record_samples (data_id INTEGER PRIMARY "
"KEY, procedure_codings BLOB NOT NULL, performers BLOB, "
"execution_start_date BLOB, execution_end_date BLOB, not_performed "
"INTEGER NOT NULL, status_coding BLOB NOT NULL, category_codings "
"BLOB, reason_codings BLOB, reasons_not_performed_codings BLOB, "
"outcome_codings BLOB, complications_codings BLOB, "
"follow_ups_codings BLOB, body_sites_codings BLOB)"
),
"quantity_sample_series": (
"CREATE TABLE quantity_sample_series (data_id INTEGER PRIMARY KEY "
"REFERENCES samples (data_id) ON DELETE CASCADE, count INTEGER NOT "
"NULL DEFAULT 0, insertion_era INTEGER, hfd_key INTEGER UNIQUE NOT "
"NULL)"
),
"quantity_sample_statistics": (
"CREATE TABLE quantity_sample_statistics (owner_id INTEGER PRIMARY "
"KEY REFERENCES quantity_samples (data_id) ON DELETE CASCADE, min "
"REAL, max REAL, most_recent REAL, most_recent_date REAL, "
"most_recent_duration REAL)"
),
"quantity_samples": (
"CREATE TABLE quantity_samples (data_id INTEGER PRIMARY KEY, "
"quantity REAL, original_quantity REAL, original_unit INTEGER "
"REFERENCES unit_strings (ROWID) ON DELETE NO ACTION)"
),
"samples": (
"CREATE TABLE samples (data_id INTEGER PRIMARY KEY, start_date "
"REAL, end_date REAL, data_type INTEGER)"
),
"schema_user_versions": (
"CREATE TABLE schema_user_versions (schema TEXT NOT NULL PRIMARY "
"KEY, version INTEGER NOT NULL, modification_date DOUBLE NOT NULL)"
),
"source_devices": (
"CREATE TABLE source_devices( ROWID INT, name TEXT, manufacturer "
"TEXT, model TEXT, hardware TEXT, firmware TEXT, software TEXT, "
"localIdentifier TEXT, FDAUDI TEXT, creation_date REAL, "
"sync_provenance INT, uuid )"
),
"sources": (
"CREATE TABLE sources( ROWID INT, uuid, bundle_id TEXT, name TEXT, "
"source_options INT, local_device INT, product_type TEXT, deleted "
"INT, mod_date REAL, provenance INT, sync_anchor INT, "
"owner_bundle_id TEXT )"
),
"unit_strings": (
"CREATE TABLE unit_strings (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, unit_string TEXT UNIQUE)"
),
"unknown_record_samples": (
"CREATE TABLE unknown_record_samples (data_id INTEGER PRIMARY KEY, "
"display_name TEXT)"
),
"vaccination_record_samples": (
"CREATE TABLE vaccination_record_samples (data_id INTEGER PRIMARY "
"KEY, vaccination_codings BLOB NOT NULL, expiration_date BLOB, "
"dose_number TEXT, dose_quantity TEXT, performer TEXT, "
"body_site_codings BLOB, reaction TEXT, not_given INTEGER NOT NULL, "
"administration_date BLOB NOT NULL, status_coding BLOB, "
"patient_reported INTEGER NOT NULL, route_codings BLOB, "
"reasons_codings BLOB, reasons_not_given_codings BLOB)"
),
"workout_events": (
"CREATE TABLE workout_events (ROWID INTEGER PRIMARY KEY "
"AUTOINCREMENT, owner_id INTEGER NOT NULL REFERENCES workouts "
"(data_id) ON DELETE CASCADE, date REAL NOT NULL, type INTEGER NOT "
"NULL, duration REAL NOT NULL, metadata BLOB, session_uuid BLOB, "
"error BLOB)"
),
"workouts": (
"CREATE TABLE workouts (data_id INTEGER PRIMARY KEY, duration REAL, "
"total_energy_burned REAL, total_basal_energy_burned REAL, "
"total_distance REAL, activity_type INTEGER, goal_type INTEGER, "
"goal REAL, total_w_steps REAL, total_flights_climbed REAL, "
"condenser_version INTEGER, condenser_date REAL)"
),
}
]
REQUIRE_SCHEMA_MATCH = False
# TODO: consider adding output formatters for
# * distance conversion: miles = km * 0.621371
# * duration conversion: "hh:mm:ss"
# * temparature conversion: fahrenheit = (celcius * 1.8) + 32
# * weigth conversion: lbs = kg * 2.20462262
# * weigth conversion: stones = kg / 6.35029317
# * weigth conversion: pounds = ((stones - int(stones)) * 14) + 0.5
def _GetObjectValues(self, database, object_identifier):
"""Retrieves the object values.
Args:
database (SQLiteDatabase): database.
object_identifier (int): object identifier.
Returns:
dict[str, object]: object values.
"""
object_values = {}
for row in database.Query(self._OBJECT_VALUE_QUERY.format(object_identifier)):
key = row["key"]
value_type = row["value_type"]
if value_type == 0:
value = row["string_value"]
elif value_type == 1:
value = row["numerical_value"]
else:
raise RuntimeError(f"Unsupported value type: {value_type:d}")
object_values[key] = value
return object_values
def _ParseAchievementRow(self, parser_mediator, query, row, **unused_kwargs):
"""Parses an achievement row.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query (str): query that created the row.
row (sqlite3.Row): row.
"""
query_hash = hash(query)
event_data = IOSHealthAchievementsEventData()
event_data.template_unique_name = self._GetRowValue(
query_hash, row, "template_unique_name"
)
event_data.creation_time = self._GetCocoaTimeRowValue(
query_hash, row, "created_date"
)
# TODO: change into a date and time value.
event_data.earned_date = self._GetRowValue(query_hash, row, "earned_date")
event_data.value_in_canonical_unit = self._GetRowValue(
query_hash, row, "value_in_canonical_unit"
)
event_data.value_canonical_unit = self._GetRowValue(
query_hash, row, "value_canonical_unit"
)
event_data.creator_device = self._GetRowValue(query_hash, row, "creator_device")
event_data.synchronization_provenance = self._GetRowValue(
query_hash, row, "sync_provenance"
)
parser_mediator.ProduceEventData(event_data)
def _ParseAllWatchSleepSample(self, parser_mediator, query_hash, row):
"""Parses an all watch sleep sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
event_data = IOSHealthAllWatchSleepEventData()
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
event_data.sleep_state_code = self._GetRowValue(
query_hash, row, "category_value"
)
if (
event_data.end_time
and event_data.start_time
and event_data.end_time.timestamp is not None
and event_data.start_time.timestamp is not None
):
event_data.duration = (
event_data.end_time.timestamp - event_data.start_time.timestamp
)
parser_mediator.ProduceEventData(event_data)
def _ParseHeadphoneAudioLevelSample(
self, parser_mediator, query_hash, row, object_values
):
"""Parses a headphone audio level sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
object_values (dict[string, object]): object values.
"""
event_data = IOSHealthHeadphoneAudioEventData()
event_data.bundle_identifier = object_values.get(
"_HKPrivateMediaSourceBundleIdentifier"
)
event_data.decibels = self._GetRowValue(query_hash, row, "quantity")
event_data.device_local_identifier = self._GetRowValue(
query_hash, row, "device_local_identifier"
)
event_data.device_manufacturer = self._GetRowValue(
query_hash, row, "device_manufacturer"
)
event_data.device_model = self._GetRowValue(query_hash, row, "device_model")
event_data.device_name = self._GetRowValue(query_hash, row, "device_name")
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
parser_mediator.ProduceEventData(event_data)
def _ParseHeartRateSample(self, parser_mediator, query_hash, row, object_values):
"""Parses a heart rate sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
object_values (dict[string, object]): object values.
"""
quantity = self._GetRowValue(query_hash, row, "quantity")
device_name = self._GetRowValue(query_hash, row, "device_name")
if device_name == "__NONE__":
device_name = None
event_data = IOSHealthHeartRateEventData()
event_data.added_time = self._GetCocoaTimeRowValue(
query_hash, row, "creation_date"
)
event_data.bpm = int((quantity or 0.0) * 60.0)
event_data.context = object_values.get("_HKPrivateHeartRateContext")
event_data.device_name = device_name or None
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.hardware = self._GetRowValue(query_hash, row, "device_hardware")
event_data.manufacturer = self._GetRowValue(
query_hash, row, "device_manufacturer"
)
event_data.software_version = self._GetRowValue(
query_hash, row, "software_version"
)
event_data.source_name = self._GetRowValue(query_hash, row, "source_name")
event_data.source_options = self._GetRowValue(query_hash, row, "source_options")
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
event_data.time_zone = self._GetRowValue(query_hash, row, "tz_name")
parser_mediator.ProduceEventData(event_data)
def _ParseHeightSample(self, parser_mediator, query_hash, row):
"""Parses a height sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
quantity = self._GetRowValue(query_hash, row, "quantity")
if quantity is None:
return
event_data = IOSHealthHeightEventData()
event_data.height = quantity
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
parser_mediator.ProduceEventData(event_data)
def _ParseRestingHeartRateSample(self, parser_mediator, query_hash, row):
"""Parses a resting heart rate sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
quantity = self._GetRowValue(query_hash, row, "quantity")
if quantity is None:
return
event_data = IOSHealthRestingHeartRateEventData()
event_data.added_time = self._GetCocoaTimeRowValue(
query_hash, row, "creation_date"
)
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.hardware = self._GetRowValue(query_hash, row, "source_product_type")
event_data.resting_heart_rate = int(quantity)
event_data.source_name = self._GetRowValue(query_hash, row, "source_name")
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
parser_mediator.ProduceEventData(event_data)
def _ParseSamplesRow(
self, parser_mediator, query, row, database=None, **unused_kwargs
):
"""Parses a samples rate row.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query (str): query that created the row.
row (sqlite3.Row): row.
database (Optional[SQLiteDatabase]): database.
"""
query_hash = hash(query)
sample_type = self._GetRowValue(query_hash, row, "sample_type")
if sample_type == 2:
self._ParseHeightSample(parser_mediator, query_hash, row)
elif sample_type == 3:
self._ParseWeightSample(parser_mediator, query_hash, row)
elif sample_type == 5:
object_type = self._GetRowValue(query_hash, row, "object_type")
if object_type != 2:
sample_data_identifier = self._GetRowValue(
query_hash, row, "sample_data_identifier"
)
object_values = self._GetObjectValues(database, sample_data_identifier)
self._ParseHeartRateSample(
parser_mediator, query_hash, row, object_values
)
elif sample_type == 7:
self._ParseStepsSample(parser_mediator, query_hash, row)
elif sample_type == 63:
self._ParseAllWatchSleepSample(parser_mediator, query_hash, row)
elif sample_type == 70:
sample_data_identifier = self._GetRowValue(
query_hash, row, "sample_data_identifier"
)
object_values = self._GetObjectValues(database, sample_data_identifier)
self._ParseWristTemperatureSample(
parser_mediator, query_hash, row, object_values
)
elif sample_type == 118:
self._ParseRestingHeartRateSample(parser_mediator, query_hash, row)
elif sample_type == 173:
sample_data_identifier = self._GetRowValue(
query_hash, row, "sample_data_identifier"
)
object_values = self._GetObjectValues(database, sample_data_identifier)
if (
"_HKPrivateMetadataKeyHeadphoneAudioDataIsTransient"
not in object_values
):
self._ParseHeadphoneAudioLevelSample(
parser_mediator, query_hash, row, object_values
)
def _ParseSourceDevicesRow(self, parser_mediator, query, row, **unused_kwargs):
"""Parses a source devices row.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
query_hash = hash(query)
device_name = self._GetRowValue(query_hash, row, "name")
firmware = self._GetRowValue(query_hash, row, "firmware")
hardware = self._GetRowValue(query_hash, row, "hardware")
local_identifier = self._GetRowValue(query_hash, row, "localIdentifier")
software = self._GetRowValue(query_hash, row, "software")
if device_name == "__NONE__":
device_name = None
if local_identifier == "__NONE__":
local_identifier = None
event_data = IOSHealthSourceDevicesEventData()
event_data.added_time = self._GetCocoaTimeRowValue(
query_hash, row, "creation_date"
)
event_data.device_name = device_name or None
event_data.firmware = firmware or None
event_data.hardware = hardware or None
event_data.local_identifier = local_identifier or None
event_data.manufacturer = self._GetRowValue(query_hash, row, "manufacturer")
event_data.model = self._GetRowValue(query_hash, row, "model")
event_data.software = software or None
event_data.synchronization_provenance = self._GetRowValue(
query_hash, row, "sync_provenance"
)
parser_mediator.ProduceEventData(event_data)
def _ParseStepsSample(self, parser_mediator, query_hash, row):
"""Parses a steps sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
quantity = self._GetRowValue(query_hash, row, "quantity")
if quantity is None:
return
event_data = IOSHealthStepsEventData()
event_data.device = self._GetRowValue(query_hash, row, "origin_product_type")
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.number_of_steps = quantity
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
if (
event_data.end_time
and event_data.start_time
and event_data.end_time.timestamp is not None
and event_data.start_time.timestamp is not None
):
event_data.duration = (
event_data.end_time.timestamp - event_data.start_time.timestamp
)
parser_mediator.ProduceEventData(event_data)
def _ParseWeightSample(self, parser_mediator, query_hash, row):
"""Parses a weight sample.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
quantity = self._GetRowValue(query_hash, row, "quantity")
if quantity is None:
return
event_data = IOSHealthWeightEventData()
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
event_data.weight = self._GetRowValue(query_hash, row, "quantity")
parser_mediator.ProduceEventData(event_data)
def _ParseWorkoutsRow(self, parser_mediator, query, row, **unused_kwargs):
"""Parses a workouts row.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers and
other components, such as storage and dfVFS.
query_hash (int): hash of the query, that uniquely identifies the query that
produced the row.
row (sqlite3.Row): row that contains the sample.
"""
query_hash = hash(query)
event_data = IOSHealthWorkoutsEventData()
event_data.activity_type = self._GetRowValue(query_hash, row, "activity_type")
event_data.duration = self._GetRowValue(query_hash, row, "duration")
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.goal = self._GetRowValue(query_hash, row, "goal")
event_data.goal_type = self._GetRowValue(query_hash, row, "goal_type")
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
event_data.total_basal_energy_burned = self._GetRowValue(
query_hash, row, "total_basal_energy_burned"
)
event_data.total_distance = self._GetRowValue(query_hash, row, "total_distance")
event_data.total_energy_burned = self._GetRowValue(
query_hash, row, "total_energy_burned"
)
event_data.total_flights_climbed = self._GetRowValue(
query_hash, row, "total_flights_climbed"
)
event_data.total_weekly_steps = self._GetRowValue(
query_hash, row, "total_w_steps"
)
parser_mediator.ProduceEventData(event_data)
def _ParseWristTemperatureSample(self, parser_mediator, query, row, object_values):
"""Parses a wrist temperature sample.
Args:
parser_mediator (ParserMediator): mediates interactions.
query (str): query that created the row.
row (sqlite3.Row): row.
object_values (dict[string, object]): object values.
"""
query_hash = hash(query)
event_data = IOSHealthWristTemperatureEventData()
event_data.algorithm_version = object_values.get("HKAlgorithmVersion")
event_data.added_time = self._GetCocoaTimeRowValue(
query_hash, row, "creation_date"
)
event_data.device_hardware = self._GetRowValue(
query_hash, row, "device_hardware"
)
event_data.device_manufacturer = self._GetRowValue(
query_hash, row, "device_manufacturer"
)
event_data.device_model = self._GetRowValue(query_hash, row, "device_model")
event_data.device_name = self._GetRowValue(query_hash, row, "device_name")
event_data.end_time = self._GetCocoaTimeRowValue(query_hash, row, "end_date")
event_data.software_version = self._GetRowValue(
query_hash, row, "software_version"
)
event_data.source_name = self._GetRowValue(query_hash, row, "source_name")
event_data.start_time = self._GetCocoaTimeRowValue(
query_hash, row, "start_date"
)
event_data.surface_temperature = object_values.get(
"_HKPrivateMetadataKeySkinSurfaceTemperature"
)
event_data.wrist_temperature = self._GetRowValue(query_hash, row, "quantity")
parser_mediator.ProduceEventData(event_data)
sqlite.SQLiteParser.RegisterPlugin(IOSHealthPlugin)