____ _ _______ __
/ __ \ | /| / / __/ |/ /
/ /_/ / |/ |/ / _// /
\____/|__/|__/___/_||_/
owenclave / app/src/main/java/io/nekohasekai/sagernet/ktx/Hwid.kt
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
******************************************************************************/
package io.nekohasekai.sagernet.ktx
import android.os.Build
import io.nekohasekai.sagernet.database.DataStore
import java.util.UUID
/**
* Device HWID reporting, per the Remnawave HWID Device Limit protocol:
* https://docs.rw/features/hwid-device-limit
*
* The HWID is a client-owned, locally persisted identifier (not derived from
* hardware serials), so [resetHwid] doubles as the user-facing "new device
* identity" action for panels that enforce a per-user device limit.
*/
object Hwid {
// Panel-side validation (Remnawave v3.0.0+): /^[a-zA-Z0-9=-]{10,64}$/
private fun generate(): String = UUID.randomUUID().toString().replace("-", "")
/** Returns the persisted device HWID, generating one on first use. */
fun current(): String {
var value = DataStore.hwidValue
if (value.isEmpty()) {
value = generate()
DataStore.hwidValue = value
}
return value
}
/** Regenerates the persisted HWID, presenting this device as a new one. */
fun resetHwid(): String {
val value = generate()
DataStore.hwidValue = value
return value
}
/**
* Headers to attach to subscription requests when HWID reporting is
* enabled, either globally in settings or, via [subscriptionOverride],
* for this one subscription only. Empty when neither is enabled. Device
* fields fall back to the real device unless overridden in settings
* (fingerprint spoofing).
*/
fun headers(subscriptionOverride: Boolean = false): Map {
if (!DataStore.sendHwid && !subscriptionOverride) return emptyMap()
return mapOf(
"x-hwid" to current(),
"x-device-os" to DataStore.spoofDeviceOs.ifEmpty { "Android" },
"x-ver-os" to DataStore.spoofDeviceOsVersion.ifEmpty { Build.VERSION.RELEASE },
"x-device-model" to DataStore.spoofDeviceModel.ifEmpty { Build.MODEL },
)
}
}