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