1/** 2 * Copyright (c) 2024 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 16import vpn from '@ohos.net.vpn'; 17import { BusinessError } from '@kit.BasicServicesKit'; 18import type common from '@ohos.app.ability.common'; 19import VpnConfig from './VpnConfig'; 20import VpnConstant from './VpnConstant'; 21import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; 22import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; 23 24const MODULE_TAG: string = 'setting_vpn:VpnConnectModel:'; 25AppStorage.setOrCreate(VpnConstant.STORAGE_KEY_CONNECT_STATE, VpnConstant.VPN_STATE_NONE); 26 27/** 28 * app management service class 29 */ 30export class VpnConnectModel { 31 private static instance: VpnConnectModel; 32 33 private connection: vpn.VpnConnection | undefined = undefined; 34 private timeoutId: number = undefined; 35 private connectedVpnId: string = undefined; 36 private connectState: number = VpnConstant.VPN_STATE_NONE; 37 private replaceConnectVpnConfig: VpnConfig = undefined; 38 39 public static getInstance(): VpnConnectModel { 40 if (!this.instance) { 41 this.instance = new VpnConnectModel(); 42 } 43 return this.instance; 44 } 45 46 setReplaceConnectVpn(vpnConfig: VpnConfig): void { 47 this.replaceConnectVpnConfig = vpnConfig; 48 } 49 50 setConnectState(state: number, id?: string): void { 51 if (id) { 52 this.connectedVpnId = id; 53 } 54 LogUtil.info(MODULE_TAG + `setConnectState: ${this.connectState} -> ${state} id:${this.connectedVpnId}`); 55 this.connectState = state; 56 AppStorage.setOrCreate(VpnConstant.STORAGE_KEY_CONNECT_STATE, state); 57 } 58 59 getConnectedVpnId(): string { 60 return this.connectedVpnId; 61 } 62 63 isConnecting(vpnId: string): boolean { 64 return (vpnId === this.connectedVpnId) && 65 (this.connectState === VpnConstant.VPN_STATE_CONNECTING); 66 } 67 68 isConnectedOrConnecting(vpnId: string): boolean { 69 let connectState = this.connectState; 70 return (vpnId === this.connectedVpnId) && 71 ((connectState === VpnConstant.VPN_STATE_CONNECTING) || 72 (connectState === VpnConstant.VPN_STATE_CONNECTED)); 73 } 74 75 onConnectStateChange(isConnected: boolean): void { 76 LogUtil.info(MODULE_TAG + `onConnectStateChange isConnected=` + isConnected); 77 this.removeTimeout(); 78 if (isConnected) { 79 this.setConnectState(VpnConstant.VPN_STATE_CONNECTED); 80 return; 81 } 82 if (this.replaceConnectVpnConfig) { 83 let config = this.replaceConnectVpnConfig; 84 this.replaceConnectVpnConfig = undefined; 85 this.setUp(config); 86 return; 87 } 88 if (this.connectState === VpnConstant.VPN_STATE_CONNECTING) { 89 this.setConnectState(VpnConstant.VPN_STATE_CONNECT_FAILED); 90 return; 91 } 92 if (this.connectState === VpnConstant.VPN_STATE_DISCONNECTING) { 93 this.setConnectState(VpnConstant.VPN_STATE_DISCONNECTED); 94 } 95 } 96 97 init(context: common.UIAbilityContext): void { 98 this.connection = vpn.createVpnConnection(context); 99 try { 100 LogUtil.info(MODULE_TAG + `vpn on start`); 101 vpn.on('connect', (data) => { 102 this.onConnectStateChange(data?.isConnected); 103 }) 104 } catch (error) { 105 LogUtil.error(MODULE_TAG + `vpn on error = ${JSON.stringify(error)}`); 106 } 107 this.getConnectedVpn(); 108 } 109 110 release(): void { 111 try { 112 LogUtil.info(MODULE_TAG + `vpnConnection on subscribe off start`); 113 vpn.off('connect', (data) => { 114 LogUtil.info(MODULE_TAG + `vpnConnection off data = ${data}`); 115 }) 116 } catch (error) { 117 LogUtil.error(MODULE_TAG + `vpnConnection off error = ${JSON.stringify(error)}`); 118 } 119 } 120 121 removeTimeout(): void { 122 if (this.timeoutId !== undefined) { 123 LogUtil.info(MODULE_TAG + `removeTimeout timeoutId = ${this.timeoutId}`); 124 clearTimeout(this.timeoutId); 125 } 126 } 127 128 async setUp(vpnConfig: VpnConfig): Promise<void> { 129 if (vpnConfig === undefined || vpnConfig === null) { 130 LogUtil.info(MODULE_TAG + `setUp failed, invalid param.`); 131 return; 132 } 133 LogUtil.info(MODULE_TAG + `setUp start`); 134 this.setConnectState(VpnConstant.VPN_STATE_CONNECTING, vpnConfig.vpnId); 135 this.removeTimeout(); 136 this.timeoutId = setTimeout(() => { 137 LogUtil.info(MODULE_TAG + `setUp timeout vpnId=` + vpnConfig.vpnId); 138 this.setConnectState(VpnConstant.VPN_STATE_DISCONNECTING, vpnConfig.vpnId); 139 this.destroy((error: string) => { 140 if (error) { 141 LogUtil.error(MODULE_TAG + `vpn destroy failed, error:` + error); 142 } 143 this.setConnectState(VpnConstant.VPN_STATE_CONNECT_FAILED, vpnConfig.vpnId); 144 }); 145 }, VpnConstant.VPN_CONNECT_TIME_OUT_DURATION); 146 try { 147 await this.connection.setUp(vpnConfig); 148 } catch (err) { 149 LogUtil.error(MODULE_TAG + `setUp error = ${JSON.stringify(err)}`); 150 this.removeTimeout(); 151 VpnConfigModel.getInstance().showToast($r('app.string.vpn_error_operation_failed') + ' error:' + err); 152 this.setConnectState(VpnConstant.VPN_STATE_CONNECT_FAILED, vpnConfig.vpnId); 153 // destroy connection 154 this.destroy((error: string) => { 155 if (error) { 156 LogUtil.info(MODULE_TAG + `vpn destroy failed, error:` + error); 157 } 158 }); 159 } 160 } 161 162 getConnectedVpn(): void { 163 try { 164 LogUtil.info(MODULE_TAG + `getConnectedVpn start`); 165 vpn.getConnectedSysVpnConfig().then((data) => { 166 if (data && data.addresses && data.vpnId) { 167 this.setConnectState(VpnConstant.VPN_STATE_CONNECTED, data.vpnId); 168 } else { 169 this.setConnectState(VpnConstant.VPN_STATE_NONE); 170 } 171 }); 172 } catch (error) { 173 LogUtil.error(MODULE_TAG + `getConnectedVpn error: ${JSON.stringify(error)}`); 174 this.setConnectState(VpnConstant.VPN_STATE_NONE); 175 } 176 } 177 178 isHapAvailable(): boolean { 179 return this.timeoutId !== undefined; 180 } 181 182 destroy(callback): void { 183 this.removeTimeout(); 184 this.connection?.destroy((error: BusinessError) => { 185 if (error) { 186 LogUtil.info(MODULE_TAG + `destroy error = ${JSON.stringify(error)}`); 187 } 188 callback(error?.message); 189 }); 190 } 191 192 uint8ArrayToString(u8a: Uint8Array): string { 193 let dataStr = ""; 194 for (let i = 0; i < u8a.length; i++) { 195 dataStr += String.fromCharCode(u8a[i]) 196 } 197 return dataStr; 198 } 199}