1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.tuner.hdhomerun;
18 
19 import android.support.annotation.NonNull;
20 import android.util.Log;
21 import android.util.Pair;
22 
23 import com.android.tv.tuner.hdhomerun.HdHomeRunDiscover.HdHomeRunDiscoverDevice;
24 
25 import java.net.InetAddress;
26 import java.net.UnknownHostException;
27 import java.nio.BufferUnderflowException;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.zip.CRC32;
34 
35 class HdHomeRunUtils {
36     private static final String TAG = "HdHomeRunUtils";
37     private static final boolean DEBUG = false;
38 
39     static final int HDHOMERUN_DEVICE_TYPE_WILDCARD = 0xFFFFFFFF;
40     static final int HDHOMERUN_DEVICE_TYPE_TUNER = 0x00000001;
41     static final int HDHOMERUN_DEVICE_ID_WILDCARD = 0xFFFFFFFF;
42 
43     static final int HDHOMERUN_DISCOVER_UDP_PORT = 65001;
44     static final int HDHOMERUN_CONTROL_TCP_PORT = 65001;
45 
46     static final short HDHOMERUN_TYPE_INVALID = -1;
47     static final short HDHOMERUN_TYPE_DISCOVER_REQUEST = 0x0002;
48     static final short HDHOMERUN_TYPE_DISCOVER_REPLY = 0x0003;
49     static final short HDHOMERUN_TYPE_GETSET_REQUEST = 0x0004;
50     static final short HDHOMERUN_TYPE_GETSET_REPLY = 0x0005;
51 
52     static final byte HDHOMERUN_TAG_DEVICE_TYPE = 0x01;
53     static final byte HDHOMERUN_TAG_DEVICE_ID = 0x02;
54     static final byte HDHOMERUN_TAG_GETSET_NAME = 0x03;
55     static final int HDHOMERUN_TAG_GETSET_VALUE = 0x04;
56     static final int HDHOMERUN_TAG_ERROR_MESSAGE = 0x05;
57     static final int HDHOMERUN_TAG_TUNER_COUNT = 0x10;
58     static final int HDHOMERUN_TAG_BASE_URL = 0x2A;
59 
60     static final int HDHOMERUN_CONTROL_CONNECT_TIMEOUT_MS = 2500;
61     static final int HDHOMERUN_CONTROL_SEND_TIMEOUT_MS = 2500;
62     static final int HDHOMERUN_CONTROL_RECEIVE_TIMEOUT_MS = 2500;
63 
64     /**
65      * Finds HDHomeRun devices with given IP, type, and ID.
66      *
67      * @param targetIp {@code 0} to find target devices with broadcasting.
68      * @param deviceType The type of target devices.
69      * @param deviceId The ID of target devices.
70      * @param maxCount Maximum number of devices should be returned.
71      */
72     @NonNull
findHdHomeRunDevices( int targetIp, int deviceType, int deviceId, int maxCount)73     static List<HdHomeRunDiscoverDevice> findHdHomeRunDevices(
74             int targetIp, int deviceType, int deviceId, int maxCount) {
75         if (isIpMulticast(targetIp)) {
76             if (DEBUG) Log.d(TAG, "Target IP cannot be multicast IP.");
77             return Collections.emptyList();
78         }
79         try {
80             HdHomeRunDiscover ds = HdHomeRunDiscover.create();
81             if (ds == null) {
82                 if (DEBUG) Log.d(TAG, "Cannot create discover object.");
83                 return Collections.emptyList();
84             }
85             List<HdHomeRunDiscoverDevice> result =
86                     ds.findDevices(targetIp, deviceType, deviceId, maxCount);
87             ds.close();
88             return result;
89         } catch (Exception e) {
90             Log.w(TAG, "Failed to find HdHomeRun Devices", e);
91             return Collections.emptyList();
92         }
93     }
94 
95     /** Returns {@code true} if the given IP is a multi-cast IP. */
isIpMulticast(long ip)96     static boolean isIpMulticast(long ip) {
97         return (ip >= 0xE0000000) && (ip < 0xF0000000);
98     }
99 
100     /** Translates a {@code byte[]} address to its integer representation. */
addressToInt(byte[] address)101     static int addressToInt(byte[] address) {
102         return ByteBuffer.wrap(address).order(ByteOrder.LITTLE_ENDIAN).getInt();
103     }
104 
105     /** Translates an {@code int} address to a corresponding {@link InetAddress}. */
intToAddress(int address)106     static InetAddress intToAddress(int address) throws UnknownHostException {
107         return InetAddress.getByAddress(
108                 ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(address).array());
109     }
110 
111     /** Gets {@link String} representation of an {@code int} address. */
getIpString(int ip)112     static String getIpString(int ip) {
113         return String.format(
114                 "%d.%d.%d.%d", (ip & 0xff), (ip >> 8 & 0xff), (ip >> 16 & 0xff), (ip >> 24 & 0xff));
115     }
116 
117     /**
118      * Opens the packet returned from HDHomeRun devices to acquire the real content and verify it.
119      */
openFrame(byte[] data, int length)120     static Pair<Short, byte[]> openFrame(byte[] data, int length) {
121         if (length < 4) {
122             return null;
123         }
124         ByteBuffer buffer = ByteBuffer.wrap(data);
125         short resultType = buffer.getShort();
126         int dataLength = buffer.getShort() & 0xffff;
127 
128         if (dataLength + 8 > length) {
129             // Not finished yet.
130             return null;
131         }
132         byte[] result = new byte[dataLength];
133         buffer.get(result);
134         byte[] calculatedCrc = getCrcFromBytes(Arrays.copyOfRange(data, 0, dataLength + 4));
135         byte[] packetCrc = new byte[4];
136         buffer.get(packetCrc);
137 
138         if (!Arrays.equals(calculatedCrc, packetCrc)) {
139             return Pair.create(HDHOMERUN_TYPE_INVALID, null);
140         }
141 
142         return Pair.create(resultType, result);
143     }
144 
145     /** Seals the contents in a packet to send to HDHomeRun devices. */
sealFrame(byte[] data, short frameType)146     static byte[] sealFrame(byte[] data, short frameType) {
147         byte[] result = new byte[data.length + 8];
148         ByteBuffer buffer = ByteBuffer.wrap(result);
149         buffer.putShort(frameType);
150         buffer.putShort((short) data.length);
151         buffer.put(data);
152         buffer.put(getCrcFromBytes(Arrays.copyOfRange(result, 0, data.length + 4)));
153         return result;
154     }
155 
156     /** Reads a (tag, value) pair from packets returned from HDHomeRun devices. */
readTaggedValue(ByteBuffer buffer)157     static Pair<Byte, byte[]> readTaggedValue(ByteBuffer buffer) {
158         try {
159             Byte tag = buffer.get();
160             byte[] value = readVarLength(buffer);
161             return Pair.create(tag, value);
162         } catch (BufferUnderflowException e) {
163             return null;
164         }
165     }
166 
readVarLength(ByteBuffer buffer)167     private static byte[] readVarLength(ByteBuffer buffer) {
168         short length;
169         Byte lengthByte1 = buffer.get();
170         if ((lengthByte1 & 0x80) != 0) {
171             length = buffer.get();
172             length = (short) ((length << 7) + (lengthByte1 & 0x7F));
173         } else {
174             length = lengthByte1;
175         }
176         byte[] result = new byte[length];
177         buffer.get(result);
178         return result;
179     }
180 
getCrcFromBytes(byte[] data)181     private static byte[] getCrcFromBytes(byte[] data) {
182         CRC32 crc32 = new CRC32();
183         crc32.update(data);
184         long crc = crc32.getValue();
185         byte[] result = new byte[4];
186         for (int offset = 0; offset < 4; offset++) {
187             result[offset] = (byte) (crc & 0xFF);
188             crc >>= 8;
189         }
190         return result;
191     }
192 
HdHomeRunUtils()193     private HdHomeRunUtils() {}
194 }
195