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 android.location.cts.gnss.suplClient;
18 
19 import android.location.cts.asn1.base.PacketBuilder;
20 import android.location.cts.asn1.supl2.rrlp_messages.PDU;
21 import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
22 import android.location.cts.asn1.supl2.supl_pos.SUPLPOS;
23 import android.location.cts.asn1.supl2.supl_pos_init.NavigationModel;
24 import android.location.cts.asn1.supl2.supl_pos_init.RequestedAssistData;
25 import android.location.cts.asn1.supl2.supl_pos_init.SUPLPOSINIT;
26 import android.location.cts.asn1.supl2.supl_start.PosProtocol;
27 import android.location.cts.asn1.supl2.supl_start.PosTechnology;
28 import android.location.cts.asn1.supl2.supl_start.PrefMethod;
29 import android.location.cts.asn1.supl2.supl_start.SETCapabilities;
30 import android.location.cts.asn1.supl2.supl_start.SUPLSTART;
31 import android.location.cts.asn1.supl2.ulp.ULP_PDU;
32 import android.location.cts.asn1.supl2.ulp.UlpMessage;
33 import android.location.cts.asn1.supl2.ulp_components.CellInfo;
34 import android.location.cts.asn1.supl2.ulp_components.LocationId;
35 import android.location.cts.asn1.supl2.ulp_components.Position;
36 import android.location.cts.asn1.supl2.ulp_components.Position.timestampType;
37 import android.location.cts.asn1.supl2.ulp_components.PositionEstimate;
38 import android.location.cts.asn1.supl2.ulp_components.PositionEstimate.latitudeSignType;
39 import android.location.cts.asn1.supl2.ulp_components.SessionID;
40 import android.location.cts.asn1.supl2.ulp_components.SetSessionID;
41 import android.location.cts.asn1.supl2.ulp_components.Status;
42 import android.location.cts.asn1.supl2.ulp_components.Version;
43 import android.location.cts.asn1.supl2.ulp_components.WcdmaCellInformation;
44 
45 import java.math.BigInteger;
46 import java.net.InetAddress;
47 import java.net.UnknownHostException;
48 import java.nio.ByteBuffer;
49 import java.nio.ByteOrder;
50 import java.util.BitSet;
51 import java.util.Calendar;
52 import java.util.GregorianCalendar;
53 import java.util.Random;
54 import java.util.TimeZone;
55 
56 import javax.annotation.Nullable;
57 
58 /**
59  * A class that generates several types of GPS SUPL client payloads that can be transmitted over a
60  * GPS socket.
61  *
62  * <p>Two types of SUPL payloads are supported in this version: Local Location and WCDMA versions.
63  * However, it should be straightforward to extend this class to support other types of SUPL
64  * requests.
65  */
66 public class SuplRrlpMessagesGenerator {
67   // Scale factors used for conversion from latitude and longitude in SUPL protocol format
68   // to decimal format
69   private static final double POSITION_ESTIMATE_LAT_SCALE_FACTOR = 90.0 / 8388608.0;
70   private static final double POSITION_ESTIMATE_LNG_SCALE_FACTOR = 180.0 / 8388608.0;
71 
72   /**
73    * Generate a SUPL START message that can be send by the SUPL client to the server in the case
74    * that device location is known via a latitude and a longitude.
75    *
76    * <p>SUPL START is the first message to be send from the client to the server. The server should
77    * response to the SUPL START message with a SUPL RESPONSE message containing a SessionID.
78    *
79    */
generateSuplStartLocalLocationMessage(@ullable InetAddress ipAddress)80   public static byte[] generateSuplStartLocalLocationMessage(@Nullable InetAddress ipAddress)
81       throws UnknownHostException {
82 
83     ULP_PDU ulpPdu = new ULP_PDU();
84     Version version = ulpPdu.setVersionToNewInstance();
85     version.setMinToNewInstance().setInteger(BigInteger.ZERO);
86     version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
87     version.setServindToNewInstance().setInteger(BigInteger.ZERO);
88     ulpPdu.setVersion(version);
89 
90     SessionID sessionId = ulpPdu.setSessionIDToNewInstance();
91 
92     SetSessionID setSessionId = sessionId.setSetSessionIDToNewInstance();
93     setSessionId.setSessionIdToNewInstance()
94         .setInteger(BigInteger.valueOf(new Random().nextInt(65536)));
95     if (ipAddress == null){
96       ipAddress = InetAddress.getLocalHost();
97     }
98     byte[] ipAsbytes = ipAddress.getAddress();
99     setSessionId.setSetIdToNewInstance().setIPAddressToNewInstance().setIpv4AddressToNewInstance()
100         .setValue(ipAsbytes);
101 
102     UlpMessage message = new UlpMessage();
103     SUPLSTART suplStart = message.setMsSUPLSTARTToNewInstance();
104     SETCapabilities setCapabilities = suplStart.setSETCapabilitiesToNewInstance();
105     PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
106     posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
107     posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
108     posTechnology.setAutonomousGPSToNewInstance().setValue(true);
109     posTechnology.setAFLTToNewInstance().setValue(false);
110     posTechnology.setECIDToNewInstance().setValue(false);
111     posTechnology.setEOTDToNewInstance().setValue(false);
112     posTechnology.setOTDOAToNewInstance().setValue(false);
113 
114     setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
115 
116     PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
117     posProtocol.setTia801ToNewInstance().setValue(false);
118     posProtocol.setRrlpToNewInstance().setValue(true);
119     posProtocol.setRrcToNewInstance().setValue(false);
120 
121     LocationId locationId = suplStart.setLocationIdToNewInstance();
122     CellInfo cellInfo = locationId.setCellInfoToNewInstance();
123     cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
124     // FF-FF-FF-FF-FF-FF
125     final String macBinary = "111111111111111111111111111111111111111111111111";
126     BitSet bits = new BitSet(macBinary.length());
127     for (int i = 0; i < macBinary.length(); ++i) {
128       if (macBinary.charAt(i) == '1') {
129         bits.set(i);
130       }
131     }
132     cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
133         .setApMACAddressToNewInstance().setValue(bits);
134     locationId.setStatusToNewInstance().setValue(Status.Value.current);
135 
136     message.setMsSUPLSTART(suplStart);
137 
138     ulpPdu.setMessage(message);
139     return encodeUlp(ulpPdu);
140   }
141 
142   /**
143    * Generate a SUPL POS INIT message that can be send by the SUPL client to the server in the case
144    * that device location is known via a latitude and a longitude.
145    *
146    * <p>SUPL POS INIT is the second message to be send from the client to the server after receiving
147    * a SUPL RESPONSE containing a SessionID from the server. The SessionID received
148    * from the server response should set in the SUPL POS INIT message.
149    *
150    */
generateSuplPositionInitLocalLocationMessage(SessionID sessionId, long latE7, long lngE7)151   public static byte[] generateSuplPositionInitLocalLocationMessage(SessionID sessionId, long latE7,
152       long lngE7) {
153 
154     ULP_PDU ulpPdu = new ULP_PDU();
155     Version version = ulpPdu.setVersionToNewInstance();
156     version.setMinToNewInstance().setInteger(BigInteger.ZERO);
157     version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
158     version.setServindToNewInstance().setInteger(BigInteger.ZERO);
159     ulpPdu.setVersion(version);
160 
161     ulpPdu.setSessionID(sessionId);
162 
163     UlpMessage message = new UlpMessage();
164     SUPLPOSINIT suplPosInit = message.setMsSUPLPOSINITToNewInstance();
165     SETCapabilities setCapabilities = suplPosInit.setSETCapabilitiesToNewInstance();
166     PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
167     posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
168     posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
169     posTechnology.setAutonomousGPSToNewInstance().setValue(true);
170     posTechnology.setAFLTToNewInstance().setValue(false);
171     posTechnology.setECIDToNewInstance().setValue(false);
172     posTechnology.setEOTDToNewInstance().setValue(false);
173     posTechnology.setOTDOAToNewInstance().setValue(false);
174 
175     setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
176 
177     PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
178     posProtocol.setTia801ToNewInstance().setValue(false);
179     posProtocol.setRrlpToNewInstance().setValue(true);
180     posProtocol.setRrcToNewInstance().setValue(false);
181 
182     RequestedAssistData reqAssistData = suplPosInit.setRequestedAssistDataToNewInstance();
183 
184     reqAssistData.setAlmanacRequestedToNewInstance().setValue(false);
185     reqAssistData.setUtcModelRequestedToNewInstance().setValue(false);
186     reqAssistData.setIonosphericModelRequestedToNewInstance().setValue(true);
187     reqAssistData.setDgpsCorrectionsRequestedToNewInstance().setValue(false);
188     reqAssistData.setReferenceLocationRequestedToNewInstance().setValue(false);
189     reqAssistData.setReferenceTimeRequestedToNewInstance().setValue(true);
190     reqAssistData.setAcquisitionAssistanceRequestedToNewInstance().setValue(false);
191     reqAssistData.setRealTimeIntegrityRequestedToNewInstance().setValue(false);
192     reqAssistData.setNavigationModelRequestedToNewInstance().setValue(true);
193     NavigationModel navigationModelData = reqAssistData.setNavigationModelDataToNewInstance();
194     navigationModelData.setGpsWeekToNewInstance().setInteger(BigInteger.ZERO);
195     navigationModelData.setGpsToeToNewInstance().setInteger(BigInteger.ZERO);
196     navigationModelData.setNSATToNewInstance().setInteger(BigInteger.ZERO);
197     navigationModelData.setToeLimitToNewInstance().setInteger(BigInteger.ZERO);
198 
199     LocationId locationId = suplPosInit.setLocationIdToNewInstance();
200     CellInfo cellInfo = locationId.setCellInfoToNewInstance();
201     cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
202     // FF-FF-FF-FF-FF-FF
203     final String macBinary = "111111111111111111111111111111111111111111111111";
204     BitSet bits = new BitSet(macBinary.length());
205     for (int i = 0; i < macBinary.length(); ++i) {
206       if (macBinary.charAt(i) == '1') {
207         bits.set(i);
208       }
209     }
210     cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
211         .setApMACAddressToNewInstance().setValue(bits);
212     locationId.setStatusToNewInstance().setValue(Status.Value.current);
213 
214     Position pos = suplPosInit.setPositionToNewInstance();
215     timestampType utcTime = pos.setTimestampToNewInstance();
216     Calendar currentTime = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
217     utcTime.setYear(currentTime.get(Calendar.YEAR));
218     utcTime.setMonth(currentTime.get(Calendar.MONTH) + 1); // Calendar's MONTH starts from 0.
219     utcTime.setDay(currentTime.get(Calendar.DAY_OF_MONTH));
220     utcTime.setHour(currentTime.get(Calendar.HOUR_OF_DAY));
221     utcTime.setMinute(currentTime.get(Calendar.MINUTE));
222     utcTime.setSecond(currentTime.get(Calendar.SECOND));
223 
224     PositionEstimate posEstimate = pos.setPositionEstimateToNewInstance();
225 
226     long latSuplFormat = (long) (Math.abs(latE7) / (POSITION_ESTIMATE_LAT_SCALE_FACTOR * 1E7));
227     long lngSuplFormat = (long) (lngE7 / (POSITION_ESTIMATE_LNG_SCALE_FACTOR * 1E7));
228     posEstimate.setLatitudeToNewInstance().setInteger(BigInteger.valueOf(latSuplFormat));
229     posEstimate.setLongitudeToNewInstance().setInteger(BigInteger.valueOf(lngSuplFormat));
230     posEstimate.setLatitudeSignToNewInstance()
231         .setValue(latE7 > 0 ? latitudeSignType.Value.north : latitudeSignType.Value.south);
232 
233     message.setMsSUPLPOSINIT(suplPosInit);
234 
235     ulpPdu.setMessage(message);
236     return encodeUlp(ulpPdu);
237   }
238 
generateAssistanceDataAckMessage(SessionID sessionId)239   public static byte[] generateAssistanceDataAckMessage(SessionID sessionId) {
240     ULP_PDU ulpPdu = new ULP_PDU();
241     Version version = ulpPdu.setVersionToNewInstance();
242     version.setMinToNewInstance().setInteger(BigInteger.ZERO);
243     version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
244     version.setServindToNewInstance().setInteger(BigInteger.ZERO);
245     ulpPdu.setVersion(version);
246 
247     ulpPdu.setSessionID(sessionId);
248 
249     PDU pdu = new PDU();
250     pdu.setReferenceNumberToNewInstance();
251     pdu.getReferenceNumber().setInteger(BigInteger.ONE);
252     pdu.setComponentToNewInstance();
253     pdu.getComponent().setAssistanceDataAckToNewInstance();
254 
255     PacketBuilder payloadBuilder = new PacketBuilder();
256     try {
257       payloadBuilder.appendAll(pdu.encodePerUnaligned());
258     } catch (IllegalArgumentException | IllegalStateException | IndexOutOfBoundsException
259         | UnsupportedOperationException e) {
260       throw new RuntimeException(e);
261     }
262     PosPayLoad.rrlpPayloadType rrlpPayload = new PosPayLoad.rrlpPayloadType();
263     rrlpPayload.setValue(payloadBuilder.getPaddedBytes());
264 
265     UlpMessage message = new UlpMessage();
266     SUPLPOS suplPos = message.setMsSUPLPOSToNewInstance();
267     suplPos.setPosPayLoadToNewInstance();
268     suplPos.getPosPayLoad().setRrlpPayload(rrlpPayload);
269 
270     ulpPdu.setMessage(message);
271 
272     return encodeUlp(ulpPdu);
273   }
274 
275   /** Encodes a ULP_PDU message into bytes and sets the length field. */
encodeUlp(ULP_PDU message)276   public static byte[] encodeUlp(ULP_PDU message) {
277     message.setLengthToNewInstance();
278     message.getLength().setInteger(BigInteger.ZERO);
279     PacketBuilder messageBuilder = new PacketBuilder();
280     messageBuilder.appendAll(message.encodePerUnaligned());
281     byte[] result = messageBuilder.getPaddedBytes();
282     ByteBuffer buffer = ByteBuffer.wrap(result);
283     buffer.order(ByteOrder.BIG_ENDIAN);
284     buffer.putShort((short) result.length);
285     return buffer.array();
286   }
287 
288 }
289