Files
bachotbakwan/app/src/main/java/backone/cloud/call/Utils.java
T

579 lines
20 KiB
Java

package backone.cloud.call;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.text.TextUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
class Utils {
public static boolean hasReadPermission(Activity activity) {
return (ContextCompat.checkSelfPermission(
activity, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
}
public static boolean hasWritePermission(Activity activity) {
return (ContextCompat.checkSelfPermission(
activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
}
public static boolean hasCameraPermission(Activity activity) {
return (ContextCompat.checkSelfPermission(
activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED);
}
public static boolean hasMicPermission(Activity activity) {
return ContextCompat.checkSelfPermission(
activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}
public static void requestMicPermission(Activity activity, int requestCode) {
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.RECORD_AUDIO}, requestCode);
}
public static void requestCameraPermission(Activity activity, int request_code) {
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.CAMERA}, request_code);
}
public static void requestReadPermission(Activity activity, int request_code) {
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE}, request_code);
}
public static void requestWritePermission(Activity activity, int request_code) {
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE}, request_code);
}
public static boolean allGranted(int[] grantResults) {
if (grantResults == null || grantResults.length == 0) {
return false;
}
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
public static String getApplicationVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return "";
}
public static String join(List<String> list) {
return TextUtils.join(", ", list);
}
public static List<String> split(String str) {
List<String> out = new ArrayList<>();
if (str == null) {
return out;
}
String[] parts = str.split("\\s*,\\s*");
for (String part : parts) {
if (part == null) {
continue;
}
String trimmed = part.trim();
if (!trimmed.isEmpty()) {
out.add(trimmed);
}
}
return out;
}
private static final Pattern NAME_PATTERN = Pattern.compile("[\\w _-]+");
// check for a name that has no funny unicode characters to not let them look to much like other names
public static boolean isValidName(String name) {
if (name == null || name.length() == 0) {
return false;
}
if (!name.equals(name.trim())) {
return false;
}
return NAME_PATTERN.matcher(name).matches();
}
/** Default display name: "Ordal" plus 4 random alphanumeric characters. */
public static String generateDefaultUsername() {
final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
java.security.SecureRandom random = new java.security.SecureRandom();
StringBuilder suffix = new StringBuilder(4);
for (int i = 0; i < 4; i += 1) {
suffix.append(alphabet.charAt(random.nextInt(alphabet.length())));
}
return "Ordal" + suffix;
}
private final static char[] hexArray = "0123456789abcdef".toCharArray();
public static String byteArrayToHexString(byte[] bytes) {
if (bytes == null) {
return "";
}
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j += 1) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] hexStringToByteArray(String str) {
if (str == null || str.length() == 0 || (str.length() % 2) != 0) {
return null;
}
int len = str.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int hi = Character.digit(str.charAt(i), 16);
int lo = Character.digit(str.charAt(i + 1), 16);
if (hi < 0 || lo < 0) {
return null;
}
data[i / 2] = (byte) ((hi << 4) + lo);
}
return data;
}
public static InetSocketAddress parseInetSocketAddress(String addr, int defaultPort) {
if (addr == null || addr.length() == 0) {
return null;
}
try{
if(isIPv6(addr)){
return new InetSocketAddress(InetAddress.getByName("["+addr+"]"), defaultPort);
} else {
return new InetSocketAddress(InetAddress.getByName(addr), defaultPort);
}
} catch (Exception e) {
return null;
}
}
public static String bytesToMacAddress(byte[] mac) {
StringBuilder sb = new StringBuilder();
for (byte b : mac) {
sb.append(String.format("%02X:", b));
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
public static byte[] macAddressToBytes(String mac) {
String[] elements = mac.split(":");
byte[] array = new byte[elements.length];
for (int i = 0; i < elements.length; i += 1) {
array[i] = Integer.decode("0x" + elements[i]).byteValue();
}
return array;
}
// Check if MAC address is unicast/multicast
public static boolean isMulticastMAC(byte[] mac) {
return (mac[0] & 1) != 0;
}
// Check if MAC address is local/universal
public static boolean isUniversalMAC(byte[] mac) {
return (mac[0] & 2) == 0;
}
public static boolean isValidMAC(byte[] mac) {
// we ignore the first byte (dummy mac addresses have the "local" bit set - resulting in 0x02)
return ((mac != null)
&& (mac.length == 6)
&& ((mac[1] != 0x0) && (mac[2] != 0x0) && (mac[3] != 0x0) && (mac[4] != 0x0) && (mac[5] != 0x0))
);
}
private static boolean isHexChar(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
// check if a string is a MAC address (heuristic)
public static boolean isMAC(String address) {
if (address == null || address.length() != 17) {
return false;
}
for (int i : new int[]{0, 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16}) {
if (!isHexChar(address.charAt(i))) {
return false;
}
}
for (int i : new int[]{2, 5, 8, 11, 14}) {
if (address.charAt(i) != ':') {
return false;
}
}
return true;
}
private static final Pattern DOMAIN_PATTERN = Pattern.compile("[a-z0-9\\-.]+");
// check if string is a domain (heuristic)
public static boolean isDomain(String domain) {
if (domain == null || domain.length() == 0) {
return false;
}
if (domain.startsWith(".") || domain.endsWith(".")) {
return false;
}
if (domain.contains("..") || !domain.contains(".")) {
return false;
}
if (domain.startsWith("-") || domain.endsWith("-")) {
return false;
}
if (domain.contains(".-") || domain.contains("-.")) {
return false;
}
return DOMAIN_PATTERN.matcher(domain).matches();
}
private static final Pattern IPV4_PATTERN = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile("^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
// check if a string is an IP address (heuristic)
public static boolean isIP(String address) {
return IPV4_PATTERN.matcher(address).matches()
|| IPV6_STD_PATTERN.matcher(address).matches()
|| IPV6_HEX_COMPRESSED_PATTERN.matcher(address).matches();
}
public static boolean isIPv6(String address) {
return IPV6_STD_PATTERN.matcher(address).matches()
|| IPV6_HEX_COMPRESSED_PATTERN.matcher(address).matches();
}
public static List<AddressEntry> collectAddresses() {
ArrayList<AddressEntry> addressList = new ArrayList<>();
try {
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
String vpn = nif.getName();
if (nif.isLoopback()) {
continue;
}
for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
InetAddress addr = ia.getAddress();
if (addr.isLoopbackAddress()) {
continue;
}
String fullAddress = addr.getHostAddress();
if(fullAddress.indexOf('%')>0){
String subaddress = fullAddress.substring(0, fullAddress.indexOf('%'));
if(isIPv6(subaddress)){
subaddress= subaddress;
}
if ((vpn.contains("tun"))) {
addressList.add(new AddressEntry(subaddress, nif.getName(), addr.isMulticastAddress()));
}
} else {
if(isIPv6(fullAddress)){
fullAddress= fullAddress;
}
if ((vpn.contains("tun"))) {
addressList.add(new AddressEntry(fullAddress, nif.getName(), addr.isMulticastAddress()));
}
}
}
}
} catch (Exception ex) {
// ignore
log("error: " + ex);
}
return addressList;
}
/** Verified Bachot identity: public_key is the 4-byte TUN IPv4. */
public static boolean isIpv4IdentityKey(byte[] key) {
return key != null && key.length == 4;
}
public static String ipv4BytesToString(byte[] bytes) {
if (!isIpv4IdentityKey(bytes)) {
return null;
}
return String.format(Locale.US, "%d.%d.%d.%d",
bytes[0] & 0xFF, bytes[1] & 0xFF, bytes[2] & 0xFF, bytes[3] & 0xFF);
}
public static byte[] ipv4StringToBytes(String ipv4) {
if (ipv4 == null || !IPV4_PATTERN.matcher(ipv4).matches()) {
return null;
}
try {
byte[] bytes = InetAddress.getByName(ipv4).getAddress();
if (bytes != null && bytes.length == 4) {
return bytes;
}
} catch (Exception e) {
// ignore
}
return null;
}
/** Prefer tun0 IPv4 for verified identity; fall back to any other tun* IPv4. */
public static String getFirstTunIpv4() {
String fallback = null;
for (AddressEntry entry : collectAddresses()) {
if (entry == null || entry.address == null || entry.multicast || entry.device == null) {
continue;
}
if (!isIP(entry.address) || isIPv6(entry.address)) {
continue;
}
if ("tun0".equals(entry.device)) {
return entry.address;
}
if (fallback == null && entry.device.contains("tun")) {
fallback = entry.address;
}
}
return fallback;
}
/**
* Set own public_key + addresses from tun0 IPv4 so QR/dial match verified builds.
* @return true if settings were changed
*/
public static boolean syncIdentityFromTun(Settings settings) {
if (settings == null) {
return false;
}
String ipv4 = getFirstTunIpv4();
if (ipv4 == null) {
return false;
}
byte[] identity = ipv4StringToBytes(ipv4);
if (identity == null) {
return false;
}
boolean changed = false;
if (!Arrays.equals(settings.getPublicKey(), identity)) {
settings.setPublicKey(identity);
// IP-as-key peers use legacy plaintext signaling (no ed25519 secret).
settings.setSecretKey(null);
changed = true;
}
// Keep dial/QR address as the tun0 IPv4 only (verified ID).
List<String> addresses = settings.getAddresses();
if (addresses.size() != 1 || !ipv4.equalsIgnoreCase(addresses.get(0))) {
addresses.clear();
addresses.add(ipv4);
changed = true;
}
return changed;
}
// list all IP/MAC addresses of running network interfaces - for debugging only
public static void printOwnAddresses() {
for (AddressEntry ae : collectAddresses()) {
log("Address: " + ae.address + " (" + ae.device + (ae.multicast ? ", multicast" : "") + ")");
}
}
// Check if the given MAC address is in the IPv6 address
public static byte[] getEUI64MAC(Inet6Address addr6) {
byte[] bytes = addr6.getAddress();
if (bytes[11] != ((byte) 0xFF) || bytes[12] != ((byte) 0xFE)) {
return null;
}
byte[] mac = new byte[6];
mac[0] = (byte) (bytes[8] ^ 2);
mac[1] = bytes[9];
mac[2] = bytes[10];
mac[3] = bytes[13];
mac[4] = bytes[14];
mac[5] = bytes[15];
return mac;
}
/*
* Replace the MAC address of an EUi64 scheme IPv6 address with another MAC address.
* E.g.: ("fe80::aaaa:aaff:faa:aaa", "bb:bb:bb:bb:bb:bb") => "fe80::9bbb:bbff:febb:bbbb"
*/
private static Inet6Address createEUI64Address(Inet6Address addr6, byte[] mac) {
// addr6 is expected to be a EUI64 address
try {
byte[] bytes = addr6.getAddress();
bytes[8] = (byte) (mac[0] ^ 2);
bytes[9] = mac[1];
bytes[10] = mac[2];
// already set, but doesn't harm
bytes[11] = (byte) 0xFF;
bytes[12] = (byte) 0xFE;
bytes[13] = mac[3];
bytes[14] = mac[4];
bytes[15] = mac[5];
return Inet6Address.getByAddress(null, bytes, addr6.getScopeId());
} catch (UnknownHostException e) {
return null;
}
}
/*
* Iterate all device addresses, check if they conform to the EUI64 scheme.
* If yes, replace the MAC address in it with the supplied one and return that address.
* Also set the given port for those generated addresses.
*/
public static List<InetSocketAddress> getAddressPermutations(String contact_mac, int port) {
byte[] contact_mac_bytes = Utils.macAddressToBytes(contact_mac);
ArrayList<InetSocketAddress> addrs = new ArrayList<InetSocketAddress>();
try {
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (nif.isLoopback()) {
continue;
}
for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
InetAddress addr = ia.getAddress();
if (addr.isLoopbackAddress()) {
continue;
}
if (addr instanceof Inet6Address) {
Inet6Address addr6 = (Inet6Address) addr;
byte[] extracted_mac = getEUI64MAC(addr6);
if (extracted_mac != null && Arrays.equals(extracted_mac, nif.getHardwareAddress())) {
// We found the interface MAC address in the IPv6 assigned to that interface in the EUI-64 scheme.
// Now assume that the contact has an address with the same scheme.
InetAddress new_addr = createEUI64Address(addr6, contact_mac_bytes);
if (new_addr != null) {
addrs.add(new InetSocketAddress(new_addr, port));
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return addrs;
}
// EUI-64 based address to MAC address
public static String getGeneralizedAddress(InetAddress address) {
if (address == null) {
return "";
}
if (address instanceof Inet6Address) {
// if the IPv6 address contains a MAC address, take that.
byte[] mac = Utils.getEUI64MAC((Inet6Address) address);
if (mac != null) {
return Utils.bytesToMacAddress(mac);
}
}
String host = address.getHostAddress();
return host != null ? host : "";
}
// write file to external storage
public static void writeExternalFile(String filepath, byte[] data) throws IOException {
File file = new File(filepath);
if (file.exists() && file.isFile()) {
if (!file.delete()) {
throw new IOException("Failed to delete existing file: " + filepath);
}
}
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.close();
}
// read file from external storage
public static byte[] readExternalFile(String filepath) throws IOException {
File file = new File(filepath);
if (!file.exists() || !file.isFile()) {
throw new IOException("File does not exist: " + filepath);
}
FileInputStream fis = new FileInputStream(file);
int nRead;
byte[] data = new byte[16384];
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
while ((nRead = fis.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
fis.close();
return buffer.toByteArray();
}
private static void log(String s) {
Log.d(Utils.class.getSimpleName(), s);
}
}