1 /*
2  * Copyright (C) 2018 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.server.wifi.hotspot2.soap.command;
18 
19 import android.annotation.NonNull;
20 import android.util.Log;
21 
22 import org.ksoap2.serialization.PropertyInfo;
23 import org.ksoap2.serialization.SoapObject;
24 
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.Objects;
28 
29 /**
30  * Commands that the mobile device is being requested to execute, which is defined in SPP
31  * (Subscription Provisioning Protocol).
32  *
33  * For the details, refer to A.3.2 of Hotspot 2.0 rel2 technical specification.
34  */
35 public class SppCommand {
36     private static final String TAG = "PasspointSppCommand";
37     private int mSppCommandId;
38     private int mExecCommandId = -1;
39     private SppCommandData mCommandData;
40 
41     /**
42      * Marker interface to indicate data used for a SPP(Subscription Provisioning Protocol) command
43      */
44     public interface SppCommandData {
45 
46     }
47 
48     /**
49      * Commands embedded in sppPostDevDataResponse message for client to take an action.
50      */
51     public class CommandId {
52         public static final int EXEC = 0;
53         public static final int ADD_MO = 1;
54         public static final int UPDATE_NODE = 2;
55         public static final int NO_MO_UPDATE = 3;
56     }
57 
58     private static final Map<String, Integer> sCommands = new HashMap<>();
59     static {
60         sCommands.put("exec", CommandId.EXEC);
61         sCommands.put("addMO", CommandId.ADD_MO);
62         sCommands.put("updateNode", CommandId.UPDATE_NODE);
63         sCommands.put("noMOUpdate", CommandId.NO_MO_UPDATE);
64     }
65 
66     /**
67      * Execution types embedded in exec command for client to execute it.
68      */
69     public class ExecCommandId {
70         public static final int BROWSER = 0;
71         public static final int GET_CERT = 1;
72         public static final int USE_CLIENT_CERT_TLS = 2;
73         public static final int UPLOAD_MO = 3;
74     }
75 
76     private static final Map<String, Integer> sExecs = new HashMap<>();
77     static {
78         sExecs.put("launchBrowserToURI", ExecCommandId.BROWSER);
79         sExecs.put("getCertificate", ExecCommandId.GET_CERT);
80         sExecs.put("useClientCertTLS", ExecCommandId.USE_CLIENT_CERT_TLS);
81         sExecs.put("uploadMO", ExecCommandId.UPLOAD_MO);
82     }
83 
SppCommand(PropertyInfo soapResponse)84     private SppCommand(PropertyInfo soapResponse) throws IllegalArgumentException {
85         if (!sCommands.containsKey(soapResponse.getName())) {
86             throw new IllegalArgumentException("can't find the command: " + soapResponse.getName());
87         }
88         mSppCommandId = sCommands.get(soapResponse.getName());
89 
90         Log.i(TAG, "command name: " + soapResponse.getName());
91 
92         switch(mSppCommandId) {
93             case CommandId.EXEC:
94                 /*
95                  * Receipt of this element by a mobile device causes the following command
96                  * to be executed.
97                  */
98                 SoapObject subCommand = (SoapObject) soapResponse.getValue();
99                 if (subCommand.getPropertyCount() != 1) {
100                     throw new IllegalArgumentException(
101                             "more than one child element found for exec command: "
102                                     + subCommand.getPropertyCount());
103                 }
104 
105                 PropertyInfo commandInfo = new PropertyInfo();
106                 subCommand.getPropertyInfo(0, commandInfo);
107                 if (!sExecs.containsKey(commandInfo.getName())) {
108                     throw new IllegalArgumentException(
109                             "Unrecognized exec command: " + commandInfo.getName());
110                 }
111                 mExecCommandId = sExecs.get(commandInfo.getName());
112                 Log.i(TAG, "exec command: " +  commandInfo.getName());
113 
114                 switch (mExecCommandId) {
115                     case ExecCommandId.BROWSER:
116                         /*
117                          * When the mobile device receives this command, it launches its default
118                          * browser to the URI contained in this element. The URI must use HTTPS as
119                          * the protocol and must contain a FQDN.
120                          */
121                         mCommandData = BrowserUri.createInstance(commandInfo);
122                         break;
123                     case ExecCommandId.GET_CERT: //fall-through
124                     case ExecCommandId.UPLOAD_MO: //fall-through
125                     case ExecCommandId.USE_CLIENT_CERT_TLS: //fall-through
126                         /*
127                          * Command to mobile to re-negotiate the TLS connection using a client
128                          * certificate of the accepted type or Issuer to authenticate with the
129                          * Subscription server.
130                          */
131                     default:
132                         mCommandData = null;
133                         break;
134                 }
135                 break;
136             case CommandId.ADD_MO:
137                 /*
138                  * This command causes an management object in the mobile devices management tree
139                  * at the specified location to be added.
140                  * If there is already a management object at that location, the object is replaced.
141                  */
142                 mCommandData = PpsMoData.createInstance(soapResponse);
143                 break;
144             case CommandId.UPDATE_NODE:
145                 /*
146                  * This command causes the update of an interior node and its child nodes (if any)
147                  * at the location specified in the management tree URI attribute. The content of
148                  * this element is the MO node XML.
149                  */
150                 break;
151             case CommandId.NO_MO_UPDATE:
152                 /*
153                  * This response is used when there is no command to be executed nor update of
154                  * any MO required.
155                  */
156                 break;
157             default:
158                 mExecCommandId = -1;
159                 mCommandData = null;
160                 break;
161         }
162     }
163 
164     /**
165      * Create an instance of {@link SppCommand}
166      *
167      * @param soapResponse SOAP Response received from server.
168      * @return instance of {@link SppCommand}
169      */
createInstance(@onNull PropertyInfo soapResponse)170     public static SppCommand createInstance(@NonNull PropertyInfo soapResponse) {
171         SppCommand sppCommand;
172         try {
173             sppCommand = new SppCommand(soapResponse);
174         } catch (IllegalArgumentException e) {
175             Log.e(TAG, "fails to create an instance: " + e);
176             return null;
177         }
178         return sppCommand;
179     }
180 
getSppCommandId()181     public int getSppCommandId() {
182         return mSppCommandId;
183     }
184 
getExecCommandId()185     public int getExecCommandId() {
186         return mExecCommandId;
187     }
188 
getCommandData()189     public SppCommandData getCommandData() {
190         return mCommandData;
191     }
192 
193     @Override
equals(Object thatObject)194     public boolean equals(Object thatObject) {
195         if (this == thatObject) return true;
196         if (!(thatObject instanceof SppCommand)) return false;
197         SppCommand that = (SppCommand) thatObject;
198         return (mSppCommandId == that.getSppCommandId())
199                 && (mExecCommandId == that.getExecCommandId())
200                 && Objects.equals(mCommandData, that.getCommandData());
201     }
202 
203     @Override
hashCode()204     public int hashCode() {
205         return Objects.hash(mSppCommandId, mExecCommandId, mCommandData);
206     }
207 
208     @Override
toString()209     public String toString() {
210         return "SppCommand{"
211                 + "mSppCommandId=" + mSppCommandId
212                 + ", mExecCommandId=" + mExecCommandId
213                 + ", mCommandData=" + mCommandData
214                 + "}";
215     }
216 }
217