/*
*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.jsr082.obex.irdaobex;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Vector;
import javax.microedition.io.Connection;
import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.io.Connector;
import com.sun.jsr082.obex.ClientSessionImpl;
import com.sun.jsr082.obex.SessionNotifierImpl;
import com.sun.j2me.app.AppPackage;
import com.sun.j2me.io.ConnectionBaseInterface;
import com.sun.j2me.security.OBEXPermission;
/*
* Provides a wrapper for the irdaobex protocol implementation
* to answer the GCF style.
*/
public class Protocol implements ConnectionBaseInterface {
/* This class has a different security domain than the MIDlet suite */
// private static SecurityToken classSecurityToken = null;
/* Shows whether cilent permissions checked successfilly. */
private boolean clientPermitted = false;
/* Shows whether server permissions checked successfilly. */
private boolean serverPermitted = false;
/* Keeps the device properties and attributes. */
static protected IrOBEXControl control = null;
/* Host name used for the server side. */
private final String serverHost = "localhost";
/* Host name used for the client side. */
private final String clientHost = "discover";
/* Default constructor. */
public Protocol() {
}
/*
* Returns either ClientSession or SessionNotifier for OBEX connections,
* depending whether client or server URL was specified.
*
* @param name the URL for the connection (without "irdaobex:" prefix)
* @param mode only READ_WRITE mode is supported by OBEX
* @param timeouts ignored
* @return ClientSession for client URL or SessionNotifier for server URL
* @exception IllegalArgumentException if the URL specified is invalid
* @exception ConnectionNotFoundException if the target cannot be found
* @exception IOException if something goes wrong
* @exception SecurityException if access is prohibited
*/
public Connection openPrim(String name, int mode, boolean timeouts)
throws IOException {
// instantiate control class on the first time of method invocation
synchronized (Protocol.class) {
if (control == null) {
control = newIrOBEXControl();
}
}
// save the URL for later use
String url = "irdaobex:" + name;
if (!name.startsWith("//")) {
throw new IllegalArgumentException("Malformed URL: " + url);
}
// cut off the "//" prefix
name = name.substring(2);
// OBEX supports READ_WRITE mode only
if (mode != Connector.READ_WRITE) {
throw new IllegalArgumentException("Unsupported mode: " + mode);
}
String ias = "OBEX,OBEX:IrXfer"; // Default IAS
// ";ias=" indicates the beginning of the IAS list
int index = name.toLowerCase().indexOf(";ias=");
if (index != -1) {
ias = name.substring(index + ";ias=".length());
// check IAS validity
if (!checkIAS(ias)) {
throw new IllegalArgumentException("Invalid IAS: " + ias);
}
// cut off IAS from the name
name = name.substring(0, index);
}
Vector iasVector = new Vector();
ias = ias.concat(",");
while (ias.length() > 0) {
index = ias.indexOf(',');
iasVector.addElement(ias.substring(0, index));
ias = ias.substring(index + 1);
}
String[] iasArray = new String[iasVector.size()];
iasVector.copyInto(iasArray);
String host = name.toLowerCase();
boolean isServer;
int hints;
if (host.startsWith(serverHost)) {
isServer = true;
name = name.substring(serverHost.length());
hints = 0x0200;
} else if (host.startsWith(clientHost)) {
isServer = false;
name = name.substring(clientHost.length());
hints = 0;
} else {
throw new IllegalArgumentException("Malformed URL: " + url);
}
if (name.length() > 0 && name.charAt(0) == '.') {
// hint bits should follow
String hstring = name.substring(1).toUpperCase();
if (!checkHints(hstring)) {
throw new IllegalArgumentException(
"Invalid hint bits: " + hstring);
}
hints |= Integer.parseInt(hstring, 16);
hints &= 0x7f7f7f7f;
}
if (isServer) {
if (!serverPermitted) {
checkForPermission(OBEXPermission.OBEX_SERVER, url);
serverPermitted = true;
}
return new SessionNotifierImpl(
newIrOBEXNotifier(hints, iasArray, "irdaobex://" + name));
} else {
if (!clientPermitted) {
checkForPermission(OBEXPermission.OBEX_CLIENT, url);
clientPermitted = true;
}
return new ClientSessionImpl(
newIrOBEXConnection(hints, iasArray, "irdaobex://" + name));
}
}
/*
* Creates new irdaobex notifier.
* @param hints hint bits required to be set on the device
* @param ias services required to be provided by the device
* @param url URL of connection
* @return the instance of server connection
* @exception IOException if creating connection fails.
*/
protected IrOBEXNotifier newIrOBEXNotifier(int hints,
String[] iasArray, String url) throws IOException {
return control.createServerConnection(hints, iasArray);
}
/*
* Creates new irdaobex connection.
* @param hints hint bits required to be set on the device
* @param ias services required to be provided by the device
* @param url URL of connection
* @return the instance of client connection
* @exception IOException if creating connection fails.
*/
protected IrOBEXConnection newIrOBEXConnection(int hints,
String[] iasArray, String url) throws IOException {
return control.createClientConnection(hints, iasArray);
}
/*
* Creates new irdaobex control.
*/
protected IrOBEXControl newIrOBEXControl() {
return new IrOBEXControl();
}
/*
* Makes sure caller has the com.sun.midp permission set to "allowed".
* @param permission requested permission
* @param name resource name to check permissions against
* @exception IOInterruptedException if another thread interrupts the
* calling thread while this method is waiting to preempt the
* display.
*/
private void checkForPermission(OBEXPermission permission, String name)
throws InterruptedIOException {
AppPackage app = AppPackage.getInstance();
if (app != null)
{
try {
app.checkForPermission(new OBEXPermission(
permission.getName(), name));
}
catch (InterruptedException ie) {
throw new InterruptedIOException(
"Interrupted while trying to ask the user permission");
}
}
}
/*
* Checks the hint bits. The number of hex digits must be even,
* two digits minimum, eight digits maximum.
*
* @param hints hint bits passed in uppercase
* @return true if the parameter is valid, false otherwise
*/
private static boolean checkHints(String hints) {
if (hints.length() < 2 || hints.length() > 8 ||
hints.length() % 2 != 0) {
return false;
}
byte[] data = hints.getBytes();
for (int i = 0; i < data.length; i++) {
if (data[i] >= '0' && data[i] <= '9' ||
data[i] >= 'A' || data[i] <= 'F') {
continue;
}
return false;
}
return true;
}
/*
* Checks if the IAS (Information Access Service) string complies
* with the grammar.
*
* @param ias IrDA class names separated by comma
* @return true if the list is valid, false otherwise
*/
private static boolean checkIAS(String ias) {
// should not be empty, should not start or end with a comma
if (ias.length() == 0 || ias.charAt(0) == ',' ||
ias.charAt(ias.length() - 1) == ',') {
return false;
}
// add a comma to the end of the list to facilitate iteration
ias = ias.concat(",");
while (ias.length() > 0) {
int index = ias.indexOf(',');
byte[] data = ias.substring(0, index).getBytes();
ias = ias.substring(index + 1);
int hex = 0;
// parse single IrDA class name
for (int i = 0; i < data.length; i++) {
if (hex > 0) {
// hex digit is expected
if (data[i] >= '0' && data[i] <= '9' ||
data[i] >= 'A' && data[i] <= 'F' ||
data[i] >= 'a' && data[i] <= 'f') {
hex--;
continue;
}
return false;
}
if (data[i] == '%') {
// excapedOcted should follow (two hex digits)
hex = 2;
continue;
}
if (data[i] >= '0' && data[i] <= '9' || data[i] == ':' ||
data[i] >= 'A' && data[i] <= 'Z' ||
data[i] >= 'a' && data[i] <= 'z') {
continue;
}
return false;
}
if (hex > 0) {
return false;
}
}
return true;
}
}