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