1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5import android.util.Base64;
6import java.io.UnsupportedEncodingException;
7import java.lang.Comparable;
8import java.security.MessageDigest;
9import java.security.NoSuchAlgorithmException;
10import java.util.ArrayList;
11import java.util.Collections;
12import java.util.Comparator;
13import java.util.List;
14import org.json.JSONArray;
15import org.json.JSONException;
16import org.json.JSONObject;
17
18import eu.siacs.conversations.xml.Element;
19import eu.siacs.conversations.xmpp.forms.Data;
20import eu.siacs.conversations.xmpp.stanzas.IqPacket;
21
22public class ServiceDiscoveryResult {
23 public static final String TABLENAME = "discovery_results";
24 public static final String HASH = "hash";
25 public static final String VER = "ver";
26 public static final String RESULT = "result";
27
28 protected static String blankNull(String s) {
29 return s == null ? "" : s;
30 }
31
32 public static class Identity implements Comparable {
33 protected final String category;
34 protected final String type;
35 protected final String lang;
36 protected final String name;
37
38 public Identity(final String category, final String type, final String lang, final String name) {
39 this.category = category;
40 this.type = type;
41 this.lang = lang;
42 this.name = name;
43 }
44
45 public Identity(final Element el) {
46 this(
47 el.getAttribute("category"),
48 el.getAttribute("type"),
49 el.getAttribute("xml:lang"),
50 el.getAttribute("name")
51 );
52 }
53
54 public Identity(final JSONObject o) {
55 this(
56 o.optString("category", null),
57 o.optString("type", null),
58 o.optString("lang", null),
59 o.optString("name", null)
60 );
61 }
62
63 public String getCategory() {
64 return this.category;
65 }
66
67 public String getType() {
68 return this.type;
69 }
70
71 public String getLang() {
72 return this.lang;
73 }
74
75 public String getName() {
76 return this.name;
77 }
78
79 public int compareTo(Object other) {
80 Identity o = (Identity)other;
81 int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
82 if(r == 0) {
83 r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
84 }
85 if(r == 0) {
86 r = blankNull(this.getLang()).compareTo(blankNull(o.getLang()));
87 }
88 if(r == 0) {
89 r = blankNull(this.getName()).compareTo(blankNull(o.getName()));
90 }
91
92 return r;
93 }
94
95 public JSONObject toJSON() {
96 try {
97 JSONObject o = new JSONObject();
98 o.put("category", this.getCategory());
99 o.put("type", this.getType());
100 o.put("lang", this.getLang());
101 o.put("name", this.getName());
102 return o;
103 } catch(JSONException e) {
104 return null;
105 }
106 }
107 }
108
109 protected final String hash;
110 protected final byte[] ver;
111 protected final List<Identity> identities;
112 protected final List<String> features;
113 protected final List<Data> forms;
114
115 public ServiceDiscoveryResult(final IqPacket packet) {
116 this.identities = new ArrayList<>();
117 this.features = new ArrayList<>();
118 this.forms = new ArrayList<>();
119 this.hash = "sha-1"; // We only support sha-1 for now
120
121 final List<Element> elements = packet.query().getChildren();
122
123 for (final Element element : elements) {
124 if (element.getName().equals("identity")) {
125 Identity id = new Identity(element);
126 if (id.getType() != null && id.getCategory() != null) {
127 identities.add(id);
128 }
129 } else if (element.getName().equals("feature")) {
130 if (element.getAttribute("var") != null) {
131 features.add(element.getAttribute("var"));
132 }
133 } else if (element.getName().equals("x") && "jabber:x:data".equals(element.getAttribute("xmlns"))) {
134 forms.add(Data.parse(element));
135 }
136 }
137 this.ver = this.mkCapHash();
138 }
139
140 public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException {
141 this.identities = new ArrayList<>();
142 this.features = new ArrayList<>();
143 this.forms = new ArrayList<>();
144 this.hash = hash;
145 this.ver = ver;
146
147 JSONArray identities = o.optJSONArray("identities");
148 if (identities != null) {
149 for (int i = 0; i < identities.length(); i++) {
150 this.identities.add(new Identity(identities.getJSONObject(i)));
151 }
152 }
153 JSONArray features = o.optJSONArray("features");
154 if (features != null) {
155 for (int i = 0; i < features.length(); i++) {
156 this.features.add(features.getString(i));
157 }
158 }
159 }
160
161 public String getVer() {
162 return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
163 }
164
165 public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
166 this(
167 cursor.getString(cursor.getColumnIndex(HASH)),
168 Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT),
169 new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT)))
170 );
171 }
172
173 public List<Identity> getIdentities() {
174 return this.identities;
175 }
176
177 public List<String> getFeatures() {
178 return this.features;
179 }
180
181 public boolean hasIdentity(String category, String type) {
182 for(Identity id : this.getIdentities()) {
183 if((category == null || id.getCategory().equals(category)) &&
184 (type == null || id.getType().equals(type))) {
185 return true;
186 }
187 }
188
189 return false;
190 }
191
192 protected byte[] mkCapHash() {
193 StringBuilder s = new StringBuilder();
194
195 List<Identity> identities = this.getIdentities();
196 Collections.sort(identities);
197
198 for(Identity id : identities) {
199 s.append(
200 blankNull(id.getCategory()) + "/" +
201 blankNull(id.getType()) + "/" +
202 blankNull(id.getLang()) + "/" +
203 blankNull(id.getName()) + "<"
204 );
205 }
206
207 List<String> features = this.getFeatures();
208 Collections.sort(features);
209
210 for (String feature : features) {
211 s.append(feature + "<");
212 }
213
214 Collections.sort(forms, new Comparator<Data>() {
215 @Override
216 public int compare(Data lhs, Data rhs) {
217 return lhs.getFormType().compareTo(rhs.getFormType());
218 }
219 });
220
221 for(Data form : forms) {
222 s.append(form.getFormType()+"<");
223 //TODO append fields and values
224 }
225
226 MessageDigest md;
227 try {
228 md = MessageDigest.getInstance("SHA-1");
229 } catch (NoSuchAlgorithmException e) {
230 return null;
231 }
232
233 try {
234 return md.digest(s.toString().getBytes("UTF-8"));
235 } catch(UnsupportedEncodingException e) {
236 return null;
237 }
238 }
239
240 public JSONObject toJSON() {
241 try {
242 JSONObject o = new JSONObject();
243
244 JSONArray ids = new JSONArray();
245 for(Identity id : this.getIdentities()) {
246 ids.put(id.toJSON());
247 }
248 o.put("identites", ids);
249
250 o.put("features", new JSONArray(this.getFeatures()));
251
252 return o;
253 } catch(JSONException e) {
254 return null;
255 }
256 }
257
258 public ContentValues getContentValues() {
259 final ContentValues values = new ContentValues();
260 values.put(HASH, this.hash);
261 values.put(VER, getVer());
262 values.put(RESULT, this.toJSON().toString());
263 return values;
264 }
265}