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
132		this.ver = this.mkCapHash();
133	}
134
135	public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException {
136		this.identities = new ArrayList<>();
137		this.features = new ArrayList<>();
138		this.hash = hash;
139		this.ver = ver;
140
141		JSONArray identities = o.optJSONArray("identities");
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		for(int i = 0; i < features.length(); i++) {
148			this.features.add(features.getString(i));
149		}
150	}
151
152	public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
153		this(
154			cursor.getString(cursor.getColumnIndex(HASH)),
155			Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT),
156			new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT)))
157		);
158	}
159
160	public List<Identity> getIdentities() {
161		return this.identities;
162	}
163
164	public List<String> getFeatures() {
165		return this.features;
166	}
167
168	public boolean hasIdentity(String category, String type) {
169		for(Identity id : this.getIdentities()) {
170			if((category == null || id.getCategory().equals(category)) &&
171			   (type == null || id.getType().equals(type))) {
172				return true;
173			}
174		}
175
176		return false;
177	}
178
179	protected byte[] mkCapHash() {
180		StringBuilder s = new StringBuilder();
181
182		List<Identity> identities = this.getIdentities();
183		Collections.sort(identities);
184
185		for(Identity id : identities) {
186			s.append(
187				blankNull(id.getCategory()) + "/" +
188				blankNull(id.getType()) + "/" +
189				blankNull(id.getLang()) + "/" +
190				blankNull(id.getName()) + "<"
191			);
192		}
193
194		List<String> features = this.getFeatures();
195		Collections.sort(features);
196
197		for (String feature : features) {
198			s.append(feature + "<");
199		}
200
201		// TODO: data forms?
202
203		MessageDigest md;
204		try {
205			md = MessageDigest.getInstance("SHA-1");
206		} catch (NoSuchAlgorithmException e) {
207			return null;
208		}
209
210		try {
211			return md.digest(s.toString().getBytes("UTF-8"));
212		} catch(UnsupportedEncodingException e) {
213			return null;
214		}
215	}
216
217	public JSONObject toJSON() {
218		try {
219			JSONObject o = new JSONObject();
220
221			JSONArray ids = new JSONArray();
222			for(Identity id : this.getIdentities()) {
223				ids.put(id.toJSON());
224			}
225			o.put("identites", ids);
226
227			o.put("features", new JSONArray(this.getFeatures()));
228
229			return o;
230		} catch(JSONException e) {
231			return null;
232		}
233	}
234
235	public ContentValues getContentValues() {
236		final ContentValues values = new ContentValues();
237		values.put(HASH, this.hash);
238		values.put(VER, new String(Base64.encode(this.ver, Base64.DEFAULT)).trim());
239		values.put(RESULT, this.toJSON().toString());
240		return values;
241	}
242}