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.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}