Source code: com/opencloud/slee/services/sip/registrar/Registrar.java
1 package com.opencloud.slee.services.sip.registrar;
2
3 import java.util.ArrayList;
4 import java.util.GregorianCalendar;
5 import java.util.Iterator;
6 import java.util.List;
7 import java.util.ListIterator;
8 import java.util.Map;
9
10 import javax.sip.ServerTransaction;
11 import javax.sip.address.Address;
12 import javax.sip.address.URI;
13 import javax.sip.header.CSeqHeader;
14 import javax.sip.header.CallIdHeader;
15 import javax.sip.header.ContactHeader;
16 import javax.sip.header.DateHeader;
17 import javax.sip.header.ExpiresHeader;
18 import javax.sip.header.Header;
19 import javax.sip.header.HeaderAddress;
20 import javax.sip.header.ToHeader;
21 import javax.sip.message.Request;
22 import javax.sip.message.Response;
23 import javax.slee.Sbb;
24
25
26
27 /**
28 *
29 * TODO Class Description
30 *
31 * @author F.Moggia
32 */
33 public class Registrar{
34 private LocationService ls;
35 private long minExpires = 10; // seconds
36 private long maxExpires = 7200; // seconds
37
38 private String[] localDomainNames = {"nist.gov", "antd.nist.gov", "nist.gov;transport=udp"};
39 private RegistrarSbb sbb;
40
41 public Registrar(com.opencloud.slee.services.sip.registrar.RegistrarSbb sbb) {
42 this.sbb = sbb;
43 ls = new LocationService();
44
45 }
46
47 public String getDomain(URI uri) {
48 String address = uri.toString();
49
50 // get rid of user part
51 int index = address.indexOf('@');
52 if (index != -1) address = address.substring(index+1);
53
54 // get rid of protocol part
55 index = address.indexOf(':');
56 if (index != -1) address = address.substring(index+1);
57
58 // get rid of port and all that comes after
59 index = address.indexOf(':');
60 if (index != -1) address = address.substring(0, index);
61
62 return address;
63 }
64
65 private LocationService getLocationService(){
66 return ls;
67 }
68
69 public boolean isLocalDomain(URI uri) {
70 /*boolean match = false;
71 String uriDomain = getDomain(uri);
72 for (int i = 0; i < localDomainNames.length; i++ ) {
73 match = localDomainNames[i].equalsIgnoreCase(uriDomain);
74 if (match) break;
75 }
76
77 return match;*/
78 return true;
79 }
80
81 public static String getCanonicalAddress(HeaderAddress header) {
82 Address na = header.getAddress();
83
84 URI uri = na.getURI();
85
86 String addr = uri.toString();
87
88 int index = addr.indexOf(':');
89 index = addr.indexOf(':', index+1);
90 if (index != -1) {
91 // Get rid of the port
92 addr = addr.substring(0, index);
93 }
94
95 return addr;
96 }
97
98 public void processRequest(ServerTransaction txn, Request request) {
99
100 // RFC3261 ch10.3
101 try {
102
103 // Is this request for this domain?
104 if (!isLocalDomain(request.getRequestURI())) {
105 // If we are a proxy then forward to correct domain
106 // For now just return error code
107 Response response = sbb.getMessageFactory().createResponse(Response.FORBIDDEN, request);
108 txn.sendResponse(response);
109 return;
110 }
111
112 // Process require header
113
114
115 // Authenticate
116 // Authorize
117 // OK we're authorized now ;-)
118
119 // extract address-of-record
120 String sipAddressOfRecord = getCanonicalAddress((HeaderAddress)request.getHeader(ToHeader.NAME));
121
122 System.out.println("address-of-record = " + sipAddressOfRecord);
123
124 // map will be empty if user not in LS...
125 // Note we don't care if the user has a valid account in the LS, we just
126 // add them anyway.
127 Map bindings = getLocationService().getBindings(sipAddressOfRecord);
128
129 // Do we have any contact header(s)?
130 if (request.getHeader(ContactHeader.NAME) == null) {
131 // Just send OK with current bindings - this request was a query.
132 sendRegistrationOKResponse(txn, request, bindings);
133 return;
134 }
135
136 // Check contact, callid, cseq
137
138 ArrayList newContacts = getContactHeaderList(request.getHeaders(ContactHeader.NAME));
139 ExpiresHeader expiresHeader = null;
140 boolean removeAll = false;
141 CallIdHeader cidh = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
142 String callId = cidh.getCallId();
143
144 CSeqHeader cseqh = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
145 long seq = cseqh.getSequenceNumber();
146
147 expiresHeader = request.getExpires();
148
149 if (hasWildCard(newContacts)) { // This is a "Contact: *" "remove all bindings" request
150 if ((expiresHeader == null) || (expiresHeader.getExpires() != 0) || (newContacts.size() > 1)) {
151 // malformed request in RFC3261 ch10.3 step 6
152 txn.sendResponse(sbb.getMessageFactory().createResponse(Response.BAD_REQUEST, request));
153 return;
154 }
155
156 removeAll = true;
157 }
158
159
160 if (removeAll) {
161 System.out.println("Removing bindings");
162 // Go through list of current bindings
163 // if callid doesn't match - remove binding
164 // if callid matches and seq greater, remove binding.
165 Iterator it = bindings.values().iterator();
166 while (it.hasNext()) {
167 RegistrationBinding binding = (RegistrationBinding) it.next();
168 if (callId.equals(binding.getCallId())) {
169
170 if (seq > binding.getCSeq()) {
171 it.remove();
172 }
173 else {
174 txn.sendResponse(sbb.getMessageFactory().createResponse(Response.BAD_REQUEST, request));
175 return;
176 }
177 }
178 else {
179 it.remove();
180 }
181 }
182 try {
183 getLocationService().setBindings(sipAddressOfRecord, bindings);
184 } catch (LocationServiceException lse) {
185 txn.sendResponse(sbb.getMessageFactory().createResponse(Response.SERVER_INTERNAL_ERROR, request));
186 return;
187 }
188 sendRegistrationOKResponse(txn, request, bindings);
189 }
190 else {
191 // Update bindings
192 System.out.println("Updating bindings");
193 ListIterator li = newContacts.listIterator();
194
195 while (li.hasNext()) {
196 ContactHeader contact = (ContactHeader) li.next();
197
198 // get expires value, either in header or default
199 // do min-expires etc
200 long requestedExpires = 0;
201
202 if (contact.getExpires() >= 0) {
203 requestedExpires = contact.getExpires();
204 }
205 else if ((expiresHeader != null) && (expiresHeader.getExpires() >= 0)) {
206 requestedExpires = expiresHeader.getExpires();
207 }
208 else {
209 requestedExpires = 3600; //default
210 }
211
212 // If expires too large, reset to our local max
213 if (requestedExpires > maxExpires) {
214 requestedExpires = maxExpires;
215 }
216 else if ((requestedExpires > 0) && (requestedExpires < minExpires)) {
217 // requested expiry too short, send response with min-expires
218 //
219 sendIntervalTooBriefResponse(txn, request);
220 return;
221 }
222
223 // Get the q-value (preference) for this binding - default to 0.0 (min)
224 float q = 0;
225 if (contact.getQValue() != -1) q = contact.getQValue();
226 if ((q > 1) || (q < 0)) {
227 txn.sendResponse(sbb.getMessageFactory().createResponse(Response.BAD_REQUEST, request));
228 return;
229 }
230
231 // Find existing binding
232 URI uri = contact.getAddress().getURI();
233 String contactAddress = uri.toString();
234
235 RegistrationBinding binding = (RegistrationBinding)bindings.get(contactAddress);
236
237 if (binding != null) { // Update this binding
238 if (callId.equals(binding.getCallId())) {
239 if (seq <= binding.getCSeq()) {
240 // Invalid request
241 txn.sendResponse(sbb.getMessageFactory().createResponse(Response.BAD_REQUEST, request));
242 return;
243 }
244 }
245
246 if (requestedExpires == 0) {
247 System.out.println("Removing binding: " + sipAddressOfRecord + " -> " + contactAddress);
248 bindings.remove(contactAddress);
249 }
250 else {
251
252 System.out.println("Updating binding: " + sipAddressOfRecord + " -> " + contactAddress);
253 System.out.println(contact.toString());
254 binding.setCallId(callId);
255 binding.setCSeq(seq);
256 binding.setExpiryDelta(requestedExpires);
257 binding.setQValue(q);
258 // set timer for registration expiry
259 setRegistrationTimer(sipAddressOfRecord, contactAddress, requestedExpires, callId, seq);
260 }
261
262 }
263 else {
264 // Create new binding, add to list...
265 if (requestedExpires != 0) {
266 System.out.println("Adding new binding: " + sipAddressOfRecord + " -> " + contactAddress);
267 System.out.println(contact.toString());
268 RegistrationBinding newBinding = new RegistrationBinding(contactAddress, "", requestedExpires, q, callId, seq);
269 // removed comment parameter to registration binding - Address and Contact headers don't have comments in 1.1
270
271 bindings.put(contactAddress, newBinding);
272 // set timer for registration expiry
273 setRegistrationTimer(sipAddressOfRecord, contactAddress, requestedExpires, callId, seq);
274 }
275 }
276 }
277 // Update bindings, return 200 if successful, 500 on error
278
279 try {
280 getLocationService().setBindings(sipAddressOfRecord, bindings);
281 } catch (LocationServiceException lse) {
282 lse.printStackTrace();
283 txn.sendResponse(sbb.getMessageFactory().createResponse(Response.SERVER_INTERNAL_ERROR, request));
284 return;
285 }
286
287 sendRegistrationOKResponse(txn, request, bindings);
288
289 }
290
291
292 } catch (Exception e) {
293 e.printStackTrace();
294 }
295
296 }
297
298 private boolean hasWildCard(ArrayList contactHeaders) {
299 Iterator it = contactHeaders.iterator();
300 while (it.hasNext()) {
301 ContactHeader header = (ContactHeader) it.next();
302 if (header.toString().indexOf('*') > 0) return true;
303 }
304 return false;
305 }
306
307 private ArrayList getContactHeaderList(ListIterator it) {
308 ArrayList l = new ArrayList();
309 try {
310 while (it.hasNext()) {
311 l.add(it.next());
312 }
313 }catch (Exception e) {
314 System.err.println(e.toString());
315 }
316 return l;
317 }
318
319
320 /**
321 * Set a timer on a registration entry. If a timer is already set for this registration,
322 * reset it to the new timeout value
323 * @param sipAddress the public SIP address-of-record for the user
324 * @param sipContactAddress the physical contact address for this registration
325 * @param timeout expiry time (in seconds) of the registration
326 * @param callId the SIP callid of the REGISTER request
327 * @param cseq the SIP sequence number of the REGISTER request
328 */
329 void setRegistrationTimer(String sipAddress, String sipContactAddress, long timeout, String callId, long cseq) {
330 // leave empty since I am going to override this and use a SLEE timer anyway, but there could be a
331 // default impl here eg. using java.util.Timer
332 }
333
334 /**
335 * Expire registration entry, remove it from location service. This would be a callback from
336 * whatever timer is set in setRegistrationExpiry() above. Only remove a registration if the
337 * callId and cseq values match those of the original registration. If the values don't match,
338 * this means the registration has been updated by a more recent REGISTER request, so we should
339 * not change anything. The timer for the most recent REGISTER request will expire the entry.
340 * @param sipAddress the public SIP address-of-record for the user
341 * @param sipContactAddress the physical contact address for this registration
342 * @param callId the SIP callid of the REGISTER request
343 * @param cseq the SIP sequence number of the REGISTER request
344 */
345 void expireRegistration(String sipAddress, String sipContactAddress, String callId, long cseq) {
346 // find user in location service
347 try {
348 Map bindings = getLocationService().getBindings(sipAddress);
349 if (bindings == null) {
350 System.out.println("expireRegistration: user " + sipAddress + " not found.");
351 return;
352 }
353
354 // remove binding for sipContactAddress, if callId and cseq match.
355 RegistrationBinding binding = (RegistrationBinding) bindings.get(sipContactAddress);
356 if (binding == null) {
357 System.out.println("expireRegistration: registration for " + sipAddress + " -> " + sipContactAddress + " not found.");
358 return;
359 }
360
361 if (callId.equals(binding.getCallId()) && (cseq == binding.getCSeq())) {
362 // this is my registration, so I am allowed to remove it
363 RegistrationBinding removedBinding = (RegistrationBinding) bindings.remove(sipContactAddress);
364
365 // set bindings
366 getLocationService().setBindings(sipAddress, bindings);
367 System.out.println("expireRegistration: removed binding: " + sipAddress + " -> " + sipContactAddress);
368 }
369 else { // not my registration, do nothing
370 System.out.println("expireRegistration: callId/cseq for binding (" + sipAddress + " -> " + sipContactAddress + ") has been updated, not removing");
371 }
372
373 } catch (LocationServiceException lse) {
374 lse.printStackTrace();
375 }
376
377 }
378
379
380 private List getContactHeaders(Map bindings) {
381 if (bindings == null) return null;
382 ArrayList contactHeaders = new ArrayList();
383 Iterator it = bindings.values().iterator();
384 while (it.hasNext()) {
385 RegistrationBinding binding = (RegistrationBinding)it.next();
386 ContactHeader header = binding.getContactHeader(sbb.getAddressFactory(), sbb.getHeaderFactory());
387 if (header != null) {
388 contactHeaders.add(header);
389 }
390 }
391 return contactHeaders;
392
393 }
394
395
396 private void sendIntervalTooBriefResponse(ServerTransaction txn, Request request) {
397 try {
398 Response res = sbb.getMessageFactory().createResponse(423, request); // In RFC3261 but not JAIN SIP - coming in JAIN SIP 1.1??
399 res.setReasonPhrase("Interval Too Brief");
400 DateHeader dateHeader = sbb.getHeaderFactory().createDateHeader(new GregorianCalendar());
401 res.setHeader(dateHeader);
402 Header minExpiresHeader = sbb.getHeaderFactory().createHeader("Min-Expires", String.valueOf(minExpires));
403 res.addHeader(minExpiresHeader);
404 System.out.println("Sending Response:\n" + res.toString());
405 txn.sendResponse(res);
406 } catch (Exception e) {
407 System.err.println(e.toString());
408 e.printStackTrace();
409 }
410
411 }
412
413 private void sendRegistrationOKResponse(ServerTransaction txn, Request request, Map bindings) {
414 List contactHeaders = getContactHeaders(bindings);
415 try {
416 Response res = sbb.getMessageFactory().createResponse(Response.OK, request);
417
418 if ((contactHeaders != null) && (!contactHeaders.isEmpty())) {
419 System.out.println("Adding "+contactHeaders.size()+" headers");
420 for (int i = 0; i < contactHeaders.size(); i++) {
421 ContactHeader hdr = (ContactHeader) contactHeaders.get(i);
422 res.addHeader(hdr);
423 }
424 // ((SIPResponse) res).setHeaders(contactHeaders);
425 }
426 DateHeader dateHeader = sbb.getHeaderFactory().createDateHeader(new GregorianCalendar());
427 res.setHeader(dateHeader);
428 System.out.println("Sending Response:\n" + res.toString());
429 txn.sendResponse(res);
430 } catch (Exception e) {
431 System.err.println(e.toString());
432 e.printStackTrace();
433 }
434 }
435
436 public void processResponse(ServerTransaction txn, Response response) {
437 }
438 }