1 /* 2 * Copyright (C) 2015 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 android.net; 18 19 import android.content.Context; 20 import android.test.AndroidTestCase; 21 import android.util.Log; 22 import libcore.util.HexEncoding; 23 24 import java.io.IOException; 25 import java.net.DatagramPacket; 26 import java.net.DatagramSocket; 27 import java.net.InetAddress; 28 import java.net.SocketException; 29 import java.util.Arrays; 30 31 32 public class SntpClientTest extends AndroidTestCase { 33 private static final String TAG = "SntpClientTest"; 34 35 private static final int ORIGINATE_TIME_OFFSET = 24; 36 private static final int TRANSMIT_TIME_OFFSET = 40; 37 38 private static final int NTP_MODE_SERVER = 4; 39 private static final int NTP_MODE_BROADCAST = 5; 40 41 // From tcpdump (admittedly, an NTPv4 packet): 42 // 43 // Server, Leap indicator: (0), Stratum 2 (secondary reference), poll 6 (64s), precision -20 44 // Root Delay: 0.005447, Root dispersion: 0.002716, Reference-ID: 221.253.71.41 45 // Reference Timestamp: 3653932102.507969856 (2015/10/15 14:08:22) 46 // Originator Timestamp: 3653932113.576327741 (2015/10/15 14:08:33) 47 // Receive Timestamp: 3653932113.581012725 (2015/10/15 14:08:33) 48 // Transmit Timestamp: 3653932113.581012725 (2015/10/15 14:08:33) 49 // Originator - Receive Timestamp: +0.004684958 50 // Originator - Transmit Timestamp: +0.004684958 51 private static final String WORKING_VERSION4 = 52 "240206ec" + 53 "00000165" + 54 "000000b2" + 55 "ddfd4729" + 56 "d9ca9446820a5000" + 57 "d9ca9451938a3771" + 58 "d9ca945194bd3fff" + 59 "d9ca945194bd4001"; 60 61 private final SntpTestServer mServer = new SntpTestServer(); 62 private final SntpClient mClient = new SntpClient(); 63 64 private Network mNetwork; 65 66 @Override 67 protected void setUp() throws Exception { 68 super.setUp(); 69 ConnectivityManager mCM = getContext().getSystemService(ConnectivityManager.class); 70 mNetwork = mCM.getActiveNetwork(); 71 } 72 73 public void testBasicWorkingSntpClientQuery() throws Exception { 74 mServer.setServerReply(HexEncoding.decode(WORKING_VERSION4.toCharArray(), false)); 75 assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork)); 76 assertEquals(1, mServer.numRequestsReceived()); 77 assertEquals(1, mServer.numRepliesSent()); 78 } 79 80 public void testDnsResolutionFailure() throws Exception { 81 assertFalse(mClient.requestTime("ntp.server.doesnotexist.example", 5000, mNetwork)); 82 } 83 84 public void testTimeoutFailure() throws Exception { 85 mServer.clearServerReply(); 86 assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork)); 87 assertEquals(1, mServer.numRequestsReceived()); 88 assertEquals(0, mServer.numRepliesSent()); 89 } 90 91 public void testIgnoreLeapNoSync() throws Exception { 92 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 93 reply[0] |= (byte) 0xc0; 94 mServer.setServerReply(reply); 95 assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork)); 96 assertEquals(1, mServer.numRequestsReceived()); 97 assertEquals(1, mServer.numRepliesSent()); 98 } 99 100 public void testAcceptOnlyServerAndBroadcastModes() throws Exception { 101 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 102 for (int i = 0; i <= 7; i++) { 103 final String logMsg = "mode: " + i; 104 reply[0] &= (byte) 0xf8; 105 reply[0] |= (byte) i; 106 mServer.setServerReply(reply); 107 final boolean rval = mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, 108 mNetwork); 109 switch (i) { 110 case NTP_MODE_SERVER: 111 case NTP_MODE_BROADCAST: 112 assertTrue(logMsg, rval); 113 break; 114 default: 115 assertFalse(logMsg, rval); 116 break; 117 } 118 assertEquals(logMsg, 1, mServer.numRequestsReceived()); 119 assertEquals(logMsg, 1, mServer.numRepliesSent()); 120 } 121 } 122 123 public void testAcceptableStrataOnly() throws Exception { 124 final int STRATUM_MIN = 1; 125 final int STRATUM_MAX = 15; 126 127 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 128 for (int i = 0; i < 256; i++) { 129 final String logMsg = "stratum: " + i; 130 reply[1] = (byte) i; 131 mServer.setServerReply(reply); 132 final boolean rval = mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, 133 mNetwork); 134 if (STRATUM_MIN <= i && i <= STRATUM_MAX) { 135 assertTrue(logMsg, rval); 136 } else { 137 assertFalse(logMsg, rval); 138 } 139 assertEquals(logMsg, 1, mServer.numRequestsReceived()); 140 assertEquals(logMsg, 1, mServer.numRepliesSent()); 141 } 142 } 143 144 public void testZeroTransmitTime() throws Exception { 145 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 146 Arrays.fill(reply, TRANSMIT_TIME_OFFSET, TRANSMIT_TIME_OFFSET + 8, (byte) 0x00); 147 mServer.setServerReply(reply); 148 assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500, mNetwork)); 149 assertEquals(1, mServer.numRequestsReceived()); 150 assertEquals(1, mServer.numRepliesSent()); 151 } 152 153 154 private static class SntpTestServer { 155 private final Object mLock = new Object(); 156 private final DatagramSocket mSocket; 157 private final InetAddress mAddress; 158 private final int mPort; 159 private byte[] mReply; 160 private int mRcvd; 161 private int mSent; 162 private Thread mListeningThread; 163 164 public SntpTestServer() { 165 mSocket = makeSocket(); 166 mAddress = mSocket.getLocalAddress(); 167 mPort = mSocket.getLocalPort(); 168 Log.d(TAG, "testing server listening on (" + mAddress + ", " + mPort + ")"); 169 170 mListeningThread = new Thread() { 171 public void run() { 172 while (true) { 173 byte[] buffer = new byte[512]; 174 DatagramPacket ntpMsg = new DatagramPacket(buffer, buffer.length); 175 try { 176 mSocket.receive(ntpMsg); 177 } catch (IOException e) { 178 Log.e(TAG, "datagram receive error: " + e); 179 break; 180 } 181 synchronized (mLock) { 182 mRcvd++; 183 if (mReply == null) { continue; } 184 // Copy transmit timestamp into originate timestamp. 185 // TODO: bounds checking. 186 System.arraycopy(ntpMsg.getData(), TRANSMIT_TIME_OFFSET, 187 mReply, ORIGINATE_TIME_OFFSET, 8); 188 ntpMsg.setData(mReply); 189 ntpMsg.setLength(mReply.length); 190 try { 191 mSocket.send(ntpMsg); 192 } catch (IOException e) { 193 Log.e(TAG, "datagram send error: " + e); 194 break; 195 } 196 mSent++; 197 } 198 } 199 mSocket.close(); 200 } 201 }; 202 mListeningThread.start(); 203 } 204 205 private DatagramSocket makeSocket() { 206 DatagramSocket socket; 207 try { 208 socket = new DatagramSocket(0, InetAddress.getLoopbackAddress()); 209 } catch (SocketException e) { 210 Log.e(TAG, "Failed to create test server socket: " + e); 211 return null; 212 } 213 return socket; 214 } 215 216 public void clearServerReply() { 217 setServerReply(null); 218 } 219 220 public void setServerReply(byte[] reply) { 221 synchronized (mLock) { 222 mReply = reply; 223 mRcvd = 0; 224 mSent = 0; 225 } 226 } 227 228 public InetAddress getAddress() { return mAddress; } 229 public int getPort() { return mPort; } 230 public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } } 231 public int numRepliesSent() { synchronized (mLock) { return mSent; } } 232 } 233 } 234