blob: 39282ea10b4cbf31f7d2ea8c1d6b33fbee86b24d [file] [log] [blame]
// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHILL_WIFI_WAKE_ON_WIFI_H_
#define SHILL_WIFI_WAKE_ON_WIFI_H_
#include <linux/if_ether.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/cancelable_callback.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include <base/memory/ref_counted.h>
#include <base/memory/weak_ptr.h>
#include <components/timers/alarm_timer.h>
#include "shill/callbacks.h"
#include "shill/ip_address_store.h"
#include "shill/net/event_history.h"
#include "shill/net/ip_address.h"
#include "shill/net/netlink_manager.h"
#include "shill/refptr_types.h"
namespace shill {
class ByteString;
class Error;
class EventDispatcher;
class GetWakeOnPacketConnMessage;
class Metrics;
class Nl80211Message;
class PropertyStore;
class SetWakeOnPacketConnMessage;
class WiFi;
// |WakeOnWiFi| performs all wake on WiFi related tasks and logic (e.g.
// suspend/dark resume/resume logic, NIC wowlan programming via nl80211), and
// stores the state necessary to perform these actions.
//
// One of the most significant roles of |WakeOnWiFi| is performing the correct
// actions before suspend, during dark resume, and after resume in order to
// maintain system connectivity (if the relevant wake on WiFi features are
// supported and enabled). The state machines determining which actions are
// performed in these situations are described below:
//
// OnBeforeSuspend
// ================
// This function is run when Manager announces an upcoming system suspend.
//
// +--------------+
// | Yes | +----------------+
// +-------+--------+ +-->|Renew DHCP Lease|
// | Connected & | +------+---------+
// |holding expiring| |
// | DHCP lease? | v
// +------+---------+ +--------------------+
// | +-> |BeforeSuspendActions|
// | No | +--------------------+
// +---------------+
//
// OnDarkResume
// =============
// This function is run when Manager announces that the system has entered
// dark resume and that there is an upcoming system suspend.
//
// +-------------+ +------------+ Unsupported +----------+
// | Too many +----->|Wake reason?+-------------------->|Connected?|
// |dark resumes?| No +-++---------+ +-+-----+--+
// +------+------+ | | | |
// | Yes | | Disconnect/ No | | Yes
// v | | SSID | |
// +----------------+ | v | |
// | Disable Wake | | +------------+ | v
// | on WiFi & | | | Initiate |<--------------+ +--------+
// |report readiness| | |passive scan| |Get DHCP|
// +----------------+ | +-+----------+ +------->| Lease |
// | | ScanDone Yes | +--+---+-+
// +-------------------+ v | | |
// | Pattern +-------------+ +---------+ | |
// | No | Any services| Yes |Connected| | |
// | +--------------------+available for+----->| to AP? | | |
// | | | autoconnect?| +---+-----+ | |
// | | +-------------+ | | |
// | | |No | |
// v v | | |
// +--------------------+ +-------+ | | |
// |BeforeSuspendActions|<------+Timeout|<---------------+ No | |
// +--------------------+ +-------+<---------------------------+ |
// ^ |
// | +-------------------+ |
// +-------------------+ OnIPConfigUpdated/| Yes |
// |OnIPv6ConfigUpdated|<--------------------+
// +-------------------+
//
// BeforeSuspendActions
// =====================
// This function is run immediately before the system reports suspend readiness
// to Manager. This is the common "exit path" taken by OnBeforeSuspend and
// OnDarkResume before suspending.
//
// Yes +----------------------------+ +---------+
// +-----> |Set Wake on Disconnect flag,+--+ +-------+ |Report |
// | |Start Lease Renewal Timer* | | |Program| |Suspend |
// | +----------------------------+ +--> | NIC | |Readiness|
// +--------+-+ | +-+-----+ +---------+
// |Connected?| | | ^ ^
// +--------+-+ | | |Failed |
// | | v | |Success
// | +----------------------------+ | +-------+---+ |
// +-----> |Set Wake on SSID flag, +--+ | Verify +----+
// No |Start Wake To Scan Timer** | |Programming|
// +----------------------------+ +-----------+
//
// * if necessary (as indicated by caller of BeforeSuspendActions).
// ** if we want to whitelist more SSIDs than our NIC supports.
//
// OnAfterResume
// ==============
// This is run after Manager announces that the system has fully resumed from
// suspend.
//
// Wake on WiFi is disabled on the NIC if it was enabled before suspend or
// dark resume, and both the wake to scan timer and DHCP lease renewal timers
// are stopped.
class WakeOnWiFi {
public:
typedef std::pair<std::vector<uint8_t>, std::vector<uint32_t>>
SSIDFreqListPair;
typedef std::vector<std::pair<std::vector<uint8_t>, std::vector<uint32_t>>>
WakeOnSSIDResults;
// Types of triggers that we can program the NIC to wake the WiFi device.
enum WakeOnWiFiTrigger {
kWakeTriggerUnsupported = 0, // Used for reporting, not programming NIC.
kWakeTriggerPattern = 1,
kWakeTriggerDisconnect = 2,
kWakeTriggerSSID = 3
};
WakeOnWiFi(NetlinkManager *netlink_manager, EventDispatcher *dispatcher,
Metrics *metrics);
virtual ~WakeOnWiFi();
// Registers |store| with properties related to wake on WiFi.
void InitPropertyStore(PropertyStore *store);
// Starts |metrics_timer_| so that wake on WiFi related metrics are
// periodically collected.
void StartMetricsTimer();
// Enable the NIC to wake on packets received from |ip_endpoint|.
// Note: The actual programming of the NIC only happens before the system
// suspends, in |OnBeforeSuspend|.
void AddWakeOnPacketConnection(const std::string &ip_endpoint, Error *error);
// Remove rule to wake on packets received from |ip_endpoint| from the NIC.
// Note: The actual programming of the NIC only happens before the system
// suspends, in |OnBeforeSuspend|.
void RemoveWakeOnPacketConnection(const std::string &ip_endpoint,
Error *error);
// Remove all rules to wake on incoming packets from the NIC.
// Note: The actual programming of the NIC only happens before the system
// suspends, in |OnBeforeSuspend|.
void RemoveAllWakeOnPacketConnections(Error *error);
// Given a NL80211_CMD_NEW_WIPHY message |nl80211_message|, parses the
// wake on WiFi capabilities of the NIC and set relevant members of this
// WakeOnWiFi object to reflect the supported capbilities.
void ParseWakeOnWiFiCapabilities(const Nl80211Message &nl80211_message);
// Given a NL80211_CMD_NEW_WIPHY message |nl80211_message|, parses the
// wiphy index of the NIC and sets |wiphy_index_| with the parsed index.
void ParseWiphyIndex(const Nl80211Message &nl80211_message);
// Callback invoked when the system reports its wakeup reason.
//
// Arguments:
// - |netlink_message|: wakeup report message (note: must manually check
// this message to make sure it is a wakeup report message).
//
// Note: Assumes only one wakeup reason is received. If more than one is
// received, the only first one parsed will be handled.
virtual void OnWakeupReasonReceived(const NetlinkMessage &netlink_message);
// Performs pre-suspend actions relevant to wake on WiFi functionality.
//
// Arguments:
// - |is_connected|: whether the WiFi device is connected.
// - |ssid_whitelist|: list of SSIDs that the NIC will be programmed to wake
// the system on if the NIC is programmed to wake on SSID.
// - |done_callback|: callback to invoke when suspend actions have
// completed.
// - |renew_dhcp_lease_callback|: callback to invoke to initiate DHCP lease
// renewal.
// - |remove_supplicant_networks_callback|: callback to invoke
// to remove all networks from WPA supplicant.
// - |have_dhcp_lease|: whether or not there is a DHCP lease to renew.
// - |time_to_next_lease_renewal|: number of seconds until next DHCP lease
// renewal is due.
virtual void OnBeforeSuspend(
bool is_connected,
const std::vector<ByteString> &ssid_whitelist,
const ResultCallback &done_callback,
const base::Closure &renew_dhcp_lease_callback,
const base::Closure &remove_supplicant_networks_callback,
bool have_dhcp_lease,
uint32_t time_to_next_lease_renewal);
// Performs post-resume actions relevant to wake on wireless functionality.
virtual void OnAfterResume();
// Performs and post actions to be performed in dark resume.
//
// Arguments:
// - |is_connected|: whether the WiFi device is connected.
// - |ssid_whitelist|: list of SSIDs that the NIC will be programmed to wake
// the system on if the NIC is programmed to wake on SSID.
// - |done_callback|: callback to invoke when dark resume actions have
// completed.
// - |renew_dhcp_lease_callback|: callback to invoke to initiate DHCP lease
// renewal.
// - |initate_scan_callback|: callback to invoke to initiate a scan.
// - |remove_supplicant_networks_callback|: callback to invoke
// to remove all networks from WPA supplicant.
virtual void OnDarkResume(
bool is_connected,
const std::vector<ByteString> &ssid_whitelist,
const ResultCallback &done_callback,
const base::Closure &renew_dhcp_lease_callback,
const base::Closure &initiate_scan_callback,
const base::Closure &remove_supplicant_networks_callback);
// Wrapper around WakeOnWiFi::BeforeSuspendActions that checks if shill is
// currently in dark resume before invoking the function.
virtual void OnDHCPLeaseObtained(bool start_lease_renewal_timer,
uint32_t time_to_next_lease_renewal);
// Callback invoked to report whether this WiFi device is connected to
// a service after waking from suspend.
virtual void ReportConnectedToServiceAfterWake(bool is_connected);
// Called in WiFi::ScanDoneTask when there are no WiFi services available
// for auto-connect after a scan.
virtual void OnNoAutoConnectableServicesAfterScan(
const std::vector<ByteString> &ssid_whitelist,
const base::Closure &remove_supplicant_networks_callback);
bool in_dark_resume() { return in_dark_resume_; }
private:
friend class WakeOnWiFiTest; // access to several members for tests
friend class WiFiObjectTest; // netlink_manager_
// Tests that need kWakeOnWiFiDisabled.
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
WakeOnWiFiDisabled_AddWakeOnPacketConnection_ReturnsError);
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
WakeOnWiFiDisabled_RemoveWakeOnPacketConnection_ReturnsError);
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
WakeOnWiFiDisabled_RemoveAllWakeOnPacketConnections_ReturnsError);
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
ParseWiphyIndex_Success); // kDefaultWiphyIndex
// Tests that need kMaxSetWakeOnPacketRetries.
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
RetrySetWakeOnPacketConnections_LessThanMaxRetries);
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
RetrySetWakeOnPacketConnections_MaxAttemptsWithCallbackSet);
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
RetrySetWakeOnPacketConnections_MaxAttemptsCallbackUnset);
// Tests that need WakeOnWiFi::kDarkResumeActionsTimeoutMilliseconds
FRIEND_TEST(WakeOnWiFiTestWithMockDispatcher,
OnBeforeSuspend_DHCPLeaseRenewal);
// Tests that need WakeOnWiFi::kMaxDarkResumesPerPeriodShort
FRIEND_TEST(WakeOnWiFiTestWithDispatcher, OnBeforeSuspend_ClearsEventHistory);
FRIEND_TEST(WakeOnWiFiTestWithDispatcher,
OnDarkResume_NotConnected_MaxDarkResumes_ShortPeriod);
// Tests that need WakeOnWiFi::kMaxDarkResumesPerPeriodLong
FRIEND_TEST(WakeOnWiFiTestWithDispatcher,
OnDarkResume_NotConnected_MaxDarkResumes_LongPeriod);
static const char kWakeOnIPAddressPatternsNotSupported[];
static const char kWakeOnWiFiDisabled[];
static const uint32_t kDefaultWiphyIndex;
static const int kVerifyWakeOnWiFiSettingsDelayMilliseconds;
static const int kMaxSetWakeOnPacketRetries;
static const int kMetricsReportingFrequencySeconds;
static const uint32_t kDefaultWakeToScanPeriodSeconds;
static const uint32_t kDefaultNetDetectScanPeriodSeconds;
static const uint32_t kImmediateDHCPLeaseRenewalThresholdSeconds;
static const int kDarkResumeFrequencySamplingPeriodShortMinutes;
static const int kDarkResumeFrequencySamplingPeriodLongMinutes;
static const int kMaxDarkResumesPerPeriodShort;
static const int kMaxDarkResumesPerPeriodLong;
static int64_t DarkResumeActionsTimeoutMilliseconds; // non-const for testing
std::string GetWakeOnWiFiFeaturesEnabled(Error *error);
bool SetWakeOnWiFiFeaturesEnabled(const std::string &enabled, Error *error);
// Helper function to run and reset |suspend_actions_done_callback_|.
void RunAndResetSuspendActionsDoneCallback(const Error &error);
// Used for comparison of ByteString pairs in a set.
static bool ByteStringPairIsLessThan(
const std::pair<ByteString, ByteString> &lhs,
const std::pair<ByteString, ByteString> &rhs);
// Creates a mask which specifies which bytes in pattern of length
// |pattern_len| to match against. Bits |offset| to |pattern_len| - 1 are set,
// which bits 0 to bits 0 to |offset| - 1 are unset. This mask is saved in
// |mask|.
static void SetMask(ByteString *mask, uint32_t pattern_len, uint32_t offset);
// Creates a pattern and mask for a NL80211 message that programs the NIC to
// wake on packets originating from IP address |ip_addr|. The pattern and mask
// are saved in |pattern| and |mask| respectively. Returns true iff the
// pattern and mask are successfully created and written to |pattern| and
// |mask| respectively.
static bool CreateIPAddressPatternAndMask(const IPAddress &ip_addr,
ByteString *pattern,
ByteString *mask);
static void CreateIPV4PatternAndMask(const IPAddress &ip_addr,
ByteString *pattern, ByteString *mask);
static void CreateIPV6PatternAndMask(const IPAddress &ip_addr,
ByteString *pattern, ByteString *mask);
// Creates and sets an attribute in a NL80211 message |msg| which indicates
// the index of the wiphy interface to program. Returns true iff |msg| is
// successfully configured.
static bool ConfigureWiphyIndex(Nl80211Message *msg, int32_t index);
// Creates and sets attributes in an SetWakeOnPacketConnMessage |msg| so that
// the message will disable wake-on-packet functionality of the NIC with wiphy
// index |wiphy_index|. Returns true iff |msg| is successfully configured.
// NOTE: Assumes that |msg| has not been altered since construction.
static bool ConfigureDisableWakeOnWiFiMessage(SetWakeOnPacketConnMessage *msg,
uint32_t wiphy_index,
Error *error);
// Creates and sets attributes in a SetWakeOnPacketConnMessage |msg|
// so that the message will program the NIC with wiphy index |wiphy_index|
// with wake on wireless triggers in |trigs|. If |trigs| contains the
// kWakeTriggerPattern trigger, the message is configured to program the NIC
// to wake on packets from the IP addresses in |addrs|. If |trigs| contains
// the kSSID trigger, the message is configured to program the NIC to wake on
// the SSIDs in |ssid_whitelist|.
// Returns true iff |msg| is successfully configured.
// NOTE: Assumes that |msg| has not been altered since construction.
static bool ConfigureSetWakeOnWiFiSettingsMessage(
SetWakeOnPacketConnMessage *msg, const std::set<WakeOnWiFiTrigger> &trigs,
const IPAddressStore &addrs, uint32_t wiphy_index,
uint32_t net_detect_scan_period_seconds,
const std::vector<ByteString> &ssid_whitelist,
Error *error);
// Helper function to ConfigureSetWakeOnWiFiSettingsMessage that creates a
// single nested attribute inside the attribute list referenced by |patterns|
// representing a wake-on-packet pattern matching rule with index |patnum|.
// Returns true iff the attribute is successfully created and set.
// NOTE: |patterns| is assumed to reference the nested attribute list
// NL80211_WOWLAN_TRIG_PKT_PATTERN.
// NOTE: |patnum| should be unique across multiple calls to this function to
// prevent the formation of a erroneous nl80211 message or the overwriting of
// pattern matching rules.
static bool CreateSinglePattern(const IPAddress &ip_addr,
AttributeListRefPtr patterns, uint8_t patnum,
Error *error);
// Creates and sets attributes in an GetWakeOnPacketConnMessage msg| so that
// the message will request for wake-on-packet settings information from the
// NIC with wiphy index |wiphy_index|. Returns true iff |msg| is successfully
// configured.
// NOTE: Assumes that |msg| has not been altered since construction.
static bool ConfigureGetWakeOnWiFiSettingsMessage(
GetWakeOnPacketConnMessage *msg, uint32_t wiphy_index, Error *error);
// Given a NL80211_CMD_GET_WOWLAN response or NL80211_CMD_SET_WOWLAN request
// |msg|, returns true iff the wake-on-wifi trigger settings in |msg| match
// those in |trigs|. Performs the following checks for the following triggers:
// - kWakeTriggerDisconnect: checks that the wake on disconnect flag is
// present and set.
// - kIPAddress: checks that source IP addresses in |msg| match those reported
// in |addrs|.
// - kSSID: checks that the SSIDs in |ssid_whitelist| and the scan interval
// |net_detect_scan_period_seconds| match those reported in |msg|.
// Note: finding a trigger is in |msg| that is not expected based on the flags
// in |trig| also counts as a mismatch.
static bool WakeOnWiFiSettingsMatch(
const Nl80211Message &msg, const std::set<WakeOnWiFiTrigger> &trigs,
const IPAddressStore &addrs, uint32_t net_detect_scan_period_seconds,
const std::vector<ByteString> &ssid_whitelist);
// Handler for NL80211 message error responses from NIC wake on WiFi setting
// programming attempts.
void OnWakeOnWiFiSettingsErrorResponse(
NetlinkManager::AuxilliaryMessageType type,
const NetlinkMessage *raw_message);
// Message handler for NL80211_CMD_SET_WOWLAN responses.
static void OnSetWakeOnPacketConnectionResponse(
const Nl80211Message &nl80211_message);
// Request wake on WiFi settings for this WiFi device.
void RequestWakeOnPacketSettings();
// Verify that the wake on WiFi settings programmed into the NIC match
// those recorded locally for this device in |wake_on_packet_connections_|,
// |wake_on_wifi_triggers_|, and |wake_on_ssid_whitelist_|.
void VerifyWakeOnWiFiSettings(const Nl80211Message &nl80211_message);
// Sends an NL80211 message to program the NIC with wake on WiFi settings
// configured in |wake_on_packet_connections_|, |wake_on_ssid_whitelist_|, and
// |wake_on_wifi_triggers_|. If |wake_on_wifi_triggers_| is empty, calls
// WakeOnWiFi::DisableWakeOnWiFi.
void ApplyWakeOnWiFiSettings();
// Helper function called by |ApplyWakeOnWiFiSettings| that sends an NL80211
// message to program the NIC to disable wake on WiFi.
void DisableWakeOnWiFi();
// Calls |ApplyWakeOnWiFiSettings| and counts this call as
// a retry. If |kMaxSetWakeOnPacketRetries| retries have already been
// performed, resets counter and returns.
void RetrySetWakeOnPacketConnections();
// Utility functions to check which wake on WiFi features are currently
// enabled based on the descriptor |wake_on_wifi_features_enabled_| and
// are supported by the NIC.
bool WakeOnPacketEnabledAndSupported();
bool WakeOnSSIDEnabledAndSupported();
// Called by metrics_timer_ to reports metrics.
void ReportMetrics();
// Actions executed before normal suspend and dark resume suspend.
//
// Arguments:
// - |is_connected|: whether the WiFi device is connected.
// - |start_lease_renewal_timer|: whether or not to start the DHCP lease
// renewal timer.
// - |time_to_next_lease_renewal|: number of seconds until next DHCP lease
// renewal is due.
// - |remove_supplicant_networks_callback|: callback to invoke
// to remove all networks from WPA supplicant.
void BeforeSuspendActions(
bool is_connected,
bool start_lease_renewal_timer,
uint32_t time_to_next_lease_renewal,
const base::Closure &remove_supplicant_networks_callback);
// Needed for |dhcp_lease_renewal_timer_| and |wake_to_scan_timer_| since
// passing a empty base::Closure() causes a run-time DCHECK error when
// AlarmTimer::Start or AlarmTimer::Reset are called.
void OnTimerWakeDoNothing() {}
// Parses an attribute list containing the SSID matches that caused the
// system wake, along with the corresponding channels that these SSIDs were
// detected in. Returns a list on wake on SSID results (SSID-frequency list
// pairs) representing the reported SSID matches.
//
// Arguments:
// - |results_list|: Nested attribute list containing an array of nested
// attributes which contain the NL80211_ATTR_SSID or
// NL80211_ATTR_SCAN_FREQUENCIES attributes. This attribute list is assumed
// to have been extracted from a NL80211_CMD_SET_WOWLAN response message
// using the NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS id.
static WakeOnSSIDResults ParseWakeOnWakeOnSSIDResults(
AttributeListConstRefPtr results_list);
// Pointers to objects owned by the WiFi object that created this object.
EventDispatcher *dispatcher_;
NetlinkManager *netlink_manager_;
Metrics *metrics_;
// Executes after the NIC's wake-on-packet settings are configured via
// NL80211 messages to verify that the new configuration has taken effect.
// Calls RequestWakeOnPacketSettings.
base::CancelableClosure verify_wake_on_packet_settings_callback_;
// Callback to be invoked after all suspend actions finish executing both
// before regular suspend and before suspend in dark resume.
ResultCallback suspend_actions_done_callback_;
// Callback to report wake on WiFi related metrics.
base::CancelableClosure report_metrics_callback_;
// Number of retry attempts to program the NIC's wake-on-packet settings.
int num_set_wake_on_packet_retries_;
// Keeps track of triggers that the NIC will be programmed to wake from
// while suspended.
std::set<WakeOnWiFi::WakeOnWiFiTrigger> wake_on_wifi_triggers_;
// Keeps track of what wake on wifi triggers this WiFi device supports.
std::set<WakeOnWiFi::WakeOnWiFiTrigger> wake_on_wifi_triggers_supported_;
// Max number of patterns this WiFi device can be programmed to wake on at one
// time.
size_t wake_on_wifi_max_patterns_;
// Max number of SSIDs this WiFi device can be programmed to wake on at one
// time.
uint32_t wake_on_wifi_max_ssids_;
// Keeps track of IP addresses whose packets this device will wake upon
// receiving while the device is suspended. Only used if the NIC is programmed
// to wake on IP address patterns.
IPAddressStore wake_on_packet_connections_;
// Keeps track of SSIDs that this device will wake on the appearance of while
// the device is suspended. Only used if the NIC is programmed to wake on
// SSIDs.
std::vector<ByteString> wake_on_ssid_whitelist_;
uint32_t wiphy_index_;
bool wiphy_index_received_;
// Describes the wake on WiFi features that are currently enabled.
std::string wake_on_wifi_features_enabled_;
// Timer that wakes the system to renew DHCP leases.
timers::AlarmTimer dhcp_lease_renewal_timer_;
// Timer that wakes the system to scan for networks.
timers::AlarmTimer wake_to_scan_timer_;
// Executes when the dark resume actions timer expires. Calls
// ScanTimerHandler.
base::CancelableClosure dark_resume_actions_timeout_callback_;
// Whether shill is currently in dark resume.
bool in_dark_resume_;
// Period (in seconds) between instances where the system wakes from suspend
// to scan for networks in dark resume.
uint32_t wake_to_scan_period_seconds_;
// Period (in seconds) between instances where the NIC performs Net Detect
// scans while the system is suspended.
uint32_t net_detect_scan_period_seconds_;
// Timestamps of dark resume wakes that took place during the current
// or most recent suspend.
EventHistory dark_resume_history_;
// Last wake reason reported by the kernel.
WakeOnWiFiTrigger last_wake_reason_;
base::WeakPtrFactory<WakeOnWiFi> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(WakeOnWiFi);
};
} // namespace shill
#endif // SHILL_WIFI_WAKE_ON_WIFI_H_