ServiceDiscoveryResult.java

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