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 17package android.net; 18 19import android.net.SntpClient; 20import android.util.Log; 21import libcore.util.HexEncoding; 22 23import java.io.IOException; 24import java.net.DatagramPacket; 25import java.net.DatagramSocket; 26import java.net.InetAddress; 27import java.net.SocketException; 28import java.util.Arrays; 29import junit.framework.TestCase; 30 31 32public class SntpClientTest extends TestCase { 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 public void testBasicWorkingSntpClientQuery() throws Exception { 65 mServer.setServerReply(HexEncoding.decode(WORKING_VERSION4.toCharArray(), false)); 66 assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500)); 67 assertEquals(1, mServer.numRequestsReceived()); 68 assertEquals(1, mServer.numRepliesSent()); 69 } 70 71 public void testDnsResolutionFailure() throws Exception { 72 assertFalse(mClient.requestTime("ntp.server.doesnotexist.example", 5000)); 73 } 74 75 public void testTimeoutFailure() throws Exception { 76 mServer.clearServerReply(); 77 assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500)); 78 assertEquals(1, mServer.numRequestsReceived()); 79 assertEquals(0, mServer.numRepliesSent()); 80 } 81 82 public void testIgnoreLeapNoSync() throws Exception { 83 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 84 reply[0] |= (byte) 0xc0; 85 mServer.setServerReply(reply); 86 assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500)); 87 assertEquals(1, mServer.numRequestsReceived()); 88 assertEquals(1, mServer.numRepliesSent()); 89 } 90 91 public void testAcceptOnlyServerAndBroadcastModes() throws Exception { 92 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 93 for (int i = 0; i <= 7; i++) { 94 final String logMsg = "mode: " + i; 95 reply[0] &= (byte) 0xf8; 96 reply[0] |= (byte) i; 97 mServer.setServerReply(reply); 98 final boolean rval = mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500); 99 switch (i) { 100 case NTP_MODE_SERVER: 101 case NTP_MODE_BROADCAST: 102 assertTrue(logMsg, rval); 103 break; 104 default: 105 assertFalse(logMsg, rval); 106 break; 107 } 108 assertEquals(logMsg, 1, mServer.numRequestsReceived()); 109 assertEquals(logMsg, 1, mServer.numRepliesSent()); 110 } 111 } 112 113 public void testAcceptableStrataOnly() throws Exception { 114 final int STRATUM_MIN = 1; 115 final int STRATUM_MAX = 15; 116 117 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 118 for (int i = 0; i < 256; i++) { 119 final String logMsg = "stratum: " + i; 120 reply[1] = (byte) i; 121 mServer.setServerReply(reply); 122 final boolean rval = mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500); 123 if (STRATUM_MIN <= i && i <= STRATUM_MAX) { 124 assertTrue(logMsg, rval); 125 } else { 126 assertFalse(logMsg, rval); 127 } 128 assertEquals(logMsg, 1, mServer.numRequestsReceived()); 129 assertEquals(logMsg, 1, mServer.numRepliesSent()); 130 } 131 } 132 133 public void testZeroTransmitTime() throws Exception { 134 final byte[] reply = HexEncoding.decode(WORKING_VERSION4.toCharArray(), false); 135 Arrays.fill(reply, TRANSMIT_TIME_OFFSET, TRANSMIT_TIME_OFFSET + 8, (byte) 0x00); 136 mServer.setServerReply(reply); 137 assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500)); 138 assertEquals(1, mServer.numRequestsReceived()); 139 assertEquals(1, mServer.numRepliesSent()); 140 } 141 142 143 private static class SntpTestServer { 144 private final Object mLock = new Object(); 145 private final DatagramSocket mSocket; 146 private final InetAddress mAddress; 147 private final int mPort; 148 private byte[] mReply; 149 private int mRcvd; 150 private int mSent; 151 private Thread mListeningThread; 152 153 public SntpTestServer() { 154 mSocket = makeSocket(); 155 mAddress = mSocket.getLocalAddress(); 156 mPort = mSocket.getLocalPort(); 157 Log.d(TAG, "testing server listening on (" + mAddress + ", " + mPort + ")"); 158 159 mListeningThread = new Thread() { 160 public void run() { 161 while (true) { 162 byte[] buffer = new byte[512]; 163 DatagramPacket ntpMsg = new DatagramPacket(buffer, buffer.length); 164 try { 165 mSocket.receive(ntpMsg); 166 } catch (IOException e) { 167 Log.e(TAG, "datagram receive error: " + e); 168 break; 169 } 170 synchronized (mLock) { 171 mRcvd++; 172 if (mReply == null) { continue; } 173 // Copy transmit timestamp into originate timestamp. 174 // TODO: bounds checking. 175 System.arraycopy(ntpMsg.getData(), TRANSMIT_TIME_OFFSET, 176 mReply, ORIGINATE_TIME_OFFSET, 8); 177 ntpMsg.setData(mReply); 178 ntpMsg.setLength(mReply.length); 179 try { 180 mSocket.send(ntpMsg); 181 } catch (IOException e) { 182 Log.e(TAG, "datagram send error: " + e); 183 break; 184 } 185 mSent++; 186 } 187 } 188 mSocket.close(); 189 } 190 }; 191 mListeningThread.start(); 192 } 193 194 private DatagramSocket makeSocket() { 195 DatagramSocket socket; 196 try { 197 socket = new DatagramSocket(0, InetAddress.getLoopbackAddress()); 198 } catch (SocketException e) { 199 Log.e(TAG, "Failed to create test server socket: " + e); 200 return null; 201 } 202 return socket; 203 } 204 205 public void clearServerReply() { 206 setServerReply(null); 207 } 208 209 public void setServerReply(byte[] reply) { 210 synchronized (mLock) { 211 mReply = reply; 212 mRcvd = 0; 213 mSent = 0; 214 } 215 } 216 217 public InetAddress getAddress() { return mAddress; } 218 public int getPort() { return mPort; } 219 public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } } 220 public int numRepliesSent() { synchronized (mLock) { return mSent; } } 221 } 222} 223