chromium/ash/webui/diagnostics_ui/resources/diagnostics_utils.ts

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.js';

import {NavigationView, RoutineProperties} from './diagnostics_types.js';
import {LockType, Network, NetworkState, NetworkType} from './network_health_provider.mojom-webui.js';
import {RoutineGroup} from './routine_group.js';
import {RoutineType} from './system_routine_controller.mojom-webui.js';

/**
 * Converts a KiB storage value to GiB and returns a fixed-point string
 * to the desired number of decimal places.
 */
export function convertKibToGibDecimalString(
    value: number, numDecimalPlaces: number): string {
  return (value / 2 ** 20).toFixed(numDecimalPlaces);
}

/** Converts a KiB storage value to MiB. */
export function convertKibToMib(value: number): number {
  // 1024 KiB is 1 MiB.
  return value / (2 ** 10);
}

/** Returns an icon from the diagnostics icon set. */
export function getDiagnosticsIcon(id: string): string {
  return `diagnostics:${id}`;
}

/** Returns an icon from the navigation icon set. */
export function getNavigationIcon(id: string): string {
  return `navigation-selector:${id}`;
}

/**
 * Converts ID into matching navigation view. ID matches the 'id' field provided
 * to the navigation-view-panel {SelectorItem} array.
 */
export function getNavigationViewForPageId(id: string): NavigationView {
  switch (id) {
    case 'system':
      return NavigationView.SYSTEM;
    case 'connectivity':
      return NavigationView.CONNECTIVITY;
    case 'input':
      return NavigationView.INPUT;
    default:
      assertNotReached();
  }
}

export function getNetworkType(type: NetworkType): string {
  switch (type) {
    case NetworkType.kWiFi:
      return loadTimeData.getString('wifiLabel');
    case NetworkType.kEthernet:
      return loadTimeData.getString('ethernetLabel');
    case NetworkType.kCellular:
      return loadTimeData.getString('cellularLabel');
  }
  assertNotReached();
}

export function getNetworkState(state: NetworkState): string {
  switch (state) {
    case NetworkState.kOnline:
      return loadTimeData.getString('networkStateOnlineText');
    case NetworkState.kConnected:
      return loadTimeData.getString('networkStateConnectedText');
    case NetworkState.kPortal:
      return loadTimeData.getString('networkStatePortalText');
    case NetworkState.kConnecting:
      return loadTimeData.getString('networkStateConnectingText');
    case NetworkState.kNotConnected:
      return loadTimeData.getString('networkStateNotConnectedText');
    case NetworkState.kDisabled:
      return loadTimeData.getString('networkStateDisabledText');
  }
  assertNotReached();
}

export function getLockType(lockType: LockType): string {
  switch (lockType) {
    case LockType.kSimPuk:
      return 'sim-puk';
    case LockType.kSimPin:
      return 'sim-pin';
    case LockType.kNetworkPin:
      return 'network-pin';
    case LockType.kNone:
      return '';
  }
  assertNotReached();
}

/**
 * @param blocking If a routine is blocking, the remaining routines
 * will be skipped. For non-blocking routines, we'll continue running them
 * and display a 'WARNING' badge to signal that a non-blocking routine failed.
 */
export function createRoutine(
    routine: RoutineType, blocking: boolean): RoutineProperties {
  return {routine, blocking};
}

export function getRoutineGroups(type: NetworkType): RoutineGroup[] {
  const localNetworkGroup = new RoutineGroup(
      [
        createRoutine(RoutineType.kGatewayCanBePinged, false),
        createRoutine(RoutineType.kLanConnectivity, true),
        createRoutine(RoutineType.kArcPing, false),
      ],
      'localNetworkGroupLabel');

  const nameResolutionGroup = new RoutineGroup(
      [
        createRoutine(RoutineType.kDnsResolverPresent, true),
        createRoutine(RoutineType.kDnsResolution, true),
        createRoutine(RoutineType.kDnsLatency, true),
        createRoutine(RoutineType.kArcDnsResolution, false),
      ],
      'nameResolutionGroupLabel');

  const wifiGroup = new RoutineGroup(
      [
        createRoutine(RoutineType.kSignalStrength, false),
        createRoutine(RoutineType.kCaptivePortal, false),
        createRoutine(RoutineType.kHasSecureWiFiConnection, false),
      ],
      'wifiGroupLabel');
  const internetConnectivityGroup = new RoutineGroup(
      [
        createRoutine(RoutineType.kHttpsFirewall, true),
        createRoutine(RoutineType.kHttpFirewall, true),
        createRoutine(RoutineType.kHttpsLatency, true),
        createRoutine(RoutineType.kArcHttp, false),
      ],
      'internetConnectivityGroupLabel');

  const groupsToAdd = type === NetworkType.kWiFi ?
      [wifiGroup, internetConnectivityGroup] :
      [internetConnectivityGroup];

  const networkRoutineGroups = [
    localNetworkGroup,
    nameResolutionGroup,
  ];

  return networkRoutineGroups.concat(groupsToAdd);
}

export function getSubnetMaskFromRoutingPrefix(prefix: number): string {
  // TODO(wenyu): Handle IPv6 type with prefix range of [1, 128].

  assert(prefix >= 0 && prefix <= 32);

  // A routing prefix can not be 0. Zero indicates an unset value.
  if (prefix === 0) {
    return '';
  }

  const zeroes = 32 - prefix;
  // Note: 0xffffffff is 32 bits, all set to 1.
  // Use << to knock off |zeroes| number of bits and then use that same number
  // to replace those bits with zeroes.
  // Ex: 11111111 11111111 11111111 11111111 becomes
  // 11111111 11111111 11111111 00000000.
  let mask = (0xffffffff >> zeroes) << zeroes;

  const pieces = new Array(4);
  for (let i = 0; i < 4; i++) {
    // Note: & is binary and. 0xff is 8 ones "11111111".
    // Use & with the mask to select the bits from the other number.
    // Repeat to split the 32 bit number into four 8-bit numbers
    pieces[3 - i] = mask & 0xff;
    mask = mask >> 8;
  }

  return pieces.join('.');
}


export function formatMacAddress(macAddress: string): string {
  return `${loadTimeData.getString('macAddressLabel')}: ${macAddress}`;
}

/**
 * Resolves a networking routine type to its corresponding localized failure
 * message.
 */
export function getRoutineFailureMessage(routineType: RoutineType): string {
  switch (routineType) {
    case RoutineType.kCaptivePortal:
      return loadTimeData.getString('captivePortalFailedText');
    case RoutineType.kDnsLatency:
      return loadTimeData.getString('dnsLatencyFailedText');
    case RoutineType.kDnsResolution:
      return loadTimeData.getString('dnsResolutionFailedText');
    case RoutineType.kDnsResolverPresent:
      return loadTimeData.getString('dnsResolverPresentFailedText');
    case RoutineType.kGatewayCanBePinged:
      return loadTimeData.getString('gatewayCanBePingedFailedText');
    case RoutineType.kHasSecureWiFiConnection:
      return loadTimeData.getString('hasSecureWiFiConnectionFailedText');
    case RoutineType.kHttpFirewall:
      return loadTimeData.getString('httpFirewallFailedText');
    case RoutineType.kHttpsFirewall:
      return loadTimeData.getString('httpsFirewallFailedText');
    case RoutineType.kHttpsLatency:
      return loadTimeData.getString('httpsLatencyFailedText');
    case RoutineType.kLanConnectivity:
      return loadTimeData.getString('lanConnectivityFailedText');
    case RoutineType.kSignalStrength:
      return loadTimeData.getString('signalStrengthFailedText');
    case RoutineType.kArcHttp:
      return loadTimeData.getString('arcHttpFailedText');
    case RoutineType.kArcPing:
      return loadTimeData.getString('arcPingFailedText');
    case RoutineType.kArcDnsResolution:
      return loadTimeData.getString('arcDnsResolutionFailedText');
    case RoutineType.kBatteryCharge:
    case RoutineType.kBatteryDischarge:
    case RoutineType.kCpuCache:
    case RoutineType.kCpuStress:
    case RoutineType.kCpuFloatingPoint:
    case RoutineType.kCpuPrime:
    case RoutineType.kMemory:
    default:
      // Values should always be found in the enum.
      assertNotReached();
  }
}

export function isConnectedOrOnline(state: NetworkState): boolean {
  switch (state) {
    case NetworkState.kOnline:
    case NetworkState.kConnected:
    case NetworkState.kConnecting:
      return true;
    default:
      return false;
  }
}

export function isNetworkMissingNameServers(network: Network): boolean {
  return !network.ipConfig || !network.ipConfig.nameServers ||
      network.ipConfig.nameServers.length === 0;
}

/** Removes '0.0.0.0' from list of name servers. */
export function filterNameServers(network: Network): void {
  if (network?.ipConfig?.nameServers) {
    network.ipConfig.nameServers =
        network.ipConfig.nameServers.filter((n: string) => n !== '0.0.0.0');
  }
}

/*
 * If true network state text is appended to network and connectivity card
 * title.
 * @type {boolean}
 */
let displayStateInTitle = false;

/**
 * Test helper function to allow change if state text is appended to the card
 * title.
 */
export function setDisplayStateInTitleForTesting(state: boolean): void {
  displayStateInTitle = state;
}

/**
 * Build common string for network title for network and connectivity card.
 * Current network state is included for debugging when `displayStateInTitle`
 * is true.
 */
export function getNetworkCardTitle(
    networkType: string, networkState: string): string {
  let titleForCard = `${networkType}`;
  if (displayStateInTitle) {
    titleForCard = `${titleForCard} (${networkState})`;
  }

  return `${titleForCard}`;
}

export function getSignalStrength(value: number): string {
  assert(typeof value === 'number');
  assert(value >= 0 && value <= 100);

  if (value <= 1) {
    return '';
  }

  if (value <= 25) {
    return loadTimeData.getStringF('signalStrength_Weak', value);
  }

  if (value <= 50) {
    return loadTimeData.getStringF('signalStrength_Average', value);
  }

  if (value <= 75) {
    return loadTimeData.getStringF('signalStrength_Good', value);
  }

  return loadTimeData.getStringF('signalStrength_Excellent', value);
}