Kore/app/src/main/java/org/xbmc/kore/utils/NetUtils.java

247 lines
9.2 KiB
Java

/*
* Copyright 2015 Synced Synapse. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.utils;
import android.content.Context;
import android.os.StatFs;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
/**
* Various utilities related to networking
*/
public class NetUtils {
private static final String TAG = LogUtils.makeLogTag(NetUtils.class);
/**
* Gets an IPv4 address from a host name
* @param host The host to look up
* @return Inet4Address
*/
public static Inet4Address getInet4AddressByName(String host) throws UnknownHostException {
InetAddress[] addrs = InetAddress.getAllByName(host);
for (InetAddress addr : addrs) {
if (addr instanceof Inet4Address) {
return (Inet4Address)addr;
}
}
throw new UnknownHostException("No ipv4 address found");
}
/**
* Convert a IPv4 address from an integer to an InetAddress.
* @param hostAddress an int corresponding to the IPv4 address in network byte order
*/
public static InetAddress intToInetAddress(int hostAddress) {
if (hostAddress == 0)
return null;
byte[] addressBytes = { (byte)(0xff & hostAddress),
(byte)(0xff & (hostAddress >> 8)),
(byte)(0xff & (hostAddress >> 16)),
(byte)(0xff & (hostAddress >> 24)) };
try {
return InetAddress.getByAddress(addressBytes);
} catch (UnknownHostException e) {
throw new AssertionError();
}
}
/**
* Tries to return the MAC address of a host on the same subnet by looking at the ARP cache..
* Note: This is a synchronous call, so it should only be called on a background thread
*
* @param hostAddress Hostname or IP address
* @return MAC address if found or null
*/
public static String getMacAddress(String hostAddress) {
String ipHostAddress;
LogUtils.LOGD(TAG, "Starting get Mac Address for: " + hostAddress);
try {
InetAddress inet = InetAddress.getByName(hostAddress);
// Send some traffic, with a timeout
inet.isReachable(1000);
ipHostAddress = inet.getHostAddress();
} catch (UnknownHostException e) {
LogUtils.LOGD(TAG, "Got an UnknownHostException for host: " + hostAddress, e);
return null;
} catch (IOException e) {
LogUtils.LOGD(TAG, "Couldn't check reachability of host: " + hostAddress, e);
return null;
}
try {
// Read the arp cache
BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp"));
String arpLine;
while ((arpLine = br.readLine()) != null) {
if (arpLine.startsWith(ipHostAddress)) {
// Ok, this is the line, get the MAC Address
br.close();
return arpLine.split("\\s+")[3].toUpperCase(); // 4th element
}
}
br.close();
} catch (IOException e) {
LogUtils.LOGD(TAG, "Couldn check ARP cache.", e);
}
return null;
}
/**
* Sends a Wake On Lan magic packet to a host
* Note: This is a synchronous call, so it should only be called on a background thread
*
* @param macAddress MAC address
* @param hostAddress Hostname or IP address
* @param port Port for Wake On Lan
* @return Whether the packet was successfully sent
*/
public static boolean sendWolMagicPacket(String macAddress, String hostAddress, int port) {
if (macAddress == null) {
return false;
}
// Get MAC adress bytes
byte[] macAddressBytes = new byte[6];
String[] hex = macAddress.split("(\\:|\\-)");
if (hex.length != 6) {
LogUtils.LOGD(TAG, "Send magic packet: got an invalid MAC address: " + macAddress);
return false;
}
try {
for (int i = 0; i < 6; i++) {
macAddressBytes[i] = (byte)Integer.parseInt(hex[i], 16);
}
}
catch (NumberFormatException e) {
LogUtils.LOGD(TAG, "Send magic packet: got an invalid MAC address: " + macAddress);
return false;
}
byte[] bytes = new byte[6 + 16 * macAddressBytes.length];
for (int i = 0; i < 6; i++) {
bytes[i] = (byte)0xff;
}
for (int i = 6; i < bytes.length; i += macAddressBytes.length) {
System.arraycopy(macAddressBytes, 0, bytes, i, macAddressBytes.length);
}
try {
InetAddress address = InetAddress.getByName(hostAddress);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, port);
DatagramSocket socket = new DatagramSocket();
LogUtils.LOGD(TAG, "Sending WoL to " + address.getHostAddress() + ":" + port);
socket.send(packet);
// Piece of code apprehended from here: http://stackoverflow.com/a/29017289
// Check all the existing interfaces that can be broadcasted to
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
if (networkInterface.isLoopback())
continue; // Don't want to broadcast to the loopback interface
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
InetAddress broadcast = interfaceAddress.getBroadcast();
// Android seems smart enough to set to null broadcast to
// the external mobile network. It makes sense since Android
// silently drop UDP broadcasts involving external mobile network.
// Automatically skip IPv6 as it has broadcast IP null
if (broadcast != null) {
LogUtils.LOGD(TAG, "Sending WoL broadcast to " + broadcast.getHostAddress() + ":" + port);
// Broadcasts WOL Magic Packet to every possible broadcast address
packet = new DatagramPacket(bytes, bytes.length, broadcast, port);
socket.send(packet);
}
}
}
socket.close();
} catch (IOException e) {
LogUtils.LOGD(TAG, "Exception while sending magic packet.", e);
return false;
}
return true;
}
private static byte[] getMacBytes(String macStr) throws IllegalArgumentException {
byte[] bytes = new byte[6];
String[] hex = macStr.split("(\\:|\\-)");
if (hex.length != 6) {
throw new IllegalArgumentException("Invalid MAC address.");
}
try {
for (int i = 0; i < 6; i++) {
bytes[i] = (byte) Integer.parseInt(hex[i], 16);
}
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid hex digit in MAC address.");
}
return bytes;
}
/**
* Utility functions to create a cache for images, used with the picasso library
* Lifted from com.squareup.picasso.Utils
*/
private static final String APP_CACHE = "app-cache";
private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
public static File createDefaultCacheDir(Context context) {
File cache = new File(context.getApplicationContext().getCacheDir(), APP_CACHE);
if (!cache.exists()) {
//noinspection ResultOfMethodCallIgnored
cache.mkdirs();
}
return cache;
}
public static long calculateDiskCacheSize(File dir) {
long size = MIN_DISK_CACHE_SIZE;
try {
StatFs statFs = new StatFs(dir.getAbsolutePath());
long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();
// Target 2% of the total space.
size = available / 50;
} catch (IllegalArgumentException ignored) {
}
// Bound inside min/max size for disk cache.
return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
}
}