/* * Copyright 2009, The Android Open Source Project * Copyright (c) 2011, The Linux Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "BluetoothSocket.cpp" #include "android_bluetooth_common.h" #include "android_runtime/AndroidRuntime.h" #include "JNIHelp.h" #include "utils/Log.h" #include "cutils/abort_socket.h" #include #include #include #include #include #include #include #ifdef HAVE_BLUETOOTH #include #include #include #include #endif #define TYPE_AS_STR(t) \ ((t) == TYPE_RFCOMM ? "RFCOMM" : ((t) == TYPE_SCO ? "SCO" : \ ((t) == TYPE_L2CAP ? "L2CAP" : "EL2CAP"))) /* dscherba: HACK changing to 65000 to allow for ERTM overhead--hopefully this will not get tossed by the kernel in negotiations */ #define L2CAP_MAX_MTU 65000 namespace android { static jfieldID field_mAuth; /* read-only */ static jfieldID field_mEncrypt; /* read-only */ static jfieldID field_mType; /* read-only */ static jfieldID field_mAddress; /* read-only */ static jfieldID field_mPort; /* read-only */ static jfieldID field_mSocketData; static jmethodID method_BluetoothSocket_ctor; static jclass class_BluetoothSocket; /* Keep TYPE_RFCOMM etc in sync with BluetoothSocket.java */ static const int TYPE_RFCOMM = 1; static const int TYPE_SCO = 2; static const int TYPE_L2CAP = 3; static const int TYPE_EL2CAP = 4; static const int RFCOMM_SO_SNDBUF = 70 * 1024; // 70 KB send buffer static const int L2CAP_SO_SNDBUF = 400 * 1024; // 400 KB send buffer static const int L2CAP_SO_RCVBUF = 400 * 1024; // 400 KB receive buffer static struct asocket *get_socketData(JNIEnv *env, jobject obj) { struct asocket *s = (struct asocket *) env->GetIntField(obj, field_mSocketData); if (!s) jniThrowException(env, "java/io/IOException", "null socketData"); return s; } static void initSocketFromFdNative(JNIEnv *env, jobject obj, jint fd) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); struct asocket *s = asocket_init(fd); if (!s) { LOGV("asocket_init() failed, throwing"); jniThrowIOException(env, errno); return; } env->SetIntField(obj, field_mSocketData, (jint)s); return; #endif jniThrowIOException(env, ENOSYS); } static void initSocketNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int fd; int lm = 0; int sndbuf, rcvbuf; jboolean auth; jboolean encrypt; jint type; char value[PROPERTY_VALUE_MAX] = ""; type = env->GetIntField(obj, field_mType); switch (type) { case TYPE_RFCOMM: fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); break; case TYPE_SCO: fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); break; case TYPE_L2CAP: fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); break; case TYPE_EL2CAP: fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_L2CAP); break; default: jniThrowIOException(env, ENOSYS); return; } if (fd < 0) { LOGV("socket() failed, throwing"); jniThrowIOException(env, errno); return; } auth = env->GetBooleanField(obj, field_mAuth); encrypt = env->GetBooleanField(obj, field_mEncrypt); /* kernel does not yet support LM for SCO */ /* By default we request to be the MASTER of connection */ property_get("ro.bluetooth.request.master", value, "true"); switch (type) { case TYPE_RFCOMM: lm |= auth ? RFCOMM_LM_AUTH : 0; lm |= encrypt ? RFCOMM_LM_ENCRYPT : 0; if (!strcmp("true", value)) { LOGI("Setting Master socket option"); lm |= RFCOMM_LM_MASTER; } break; case TYPE_L2CAP: case TYPE_EL2CAP: lm |= auth ? L2CAP_LM_AUTH : 0; lm |= encrypt ? L2CAP_LM_ENCRYPT : 0; if (!strcmp("true", value)) { LOGI("Setting Master socket option"); lm |= L2CAP_LM_MASTER; } break; } if (lm) { if (type == TYPE_RFCOMM) { if (setsockopt(fd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) { LOGV("setsockopt(RFCOMM_LM) failed, throwing"); close(fd); jniThrowIOException(env, errno); return; } } else if (type == TYPE_L2CAP || type == TYPE_EL2CAP) { if (setsockopt(fd, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm))) { LOGV("setsockopt(L2CAP_LM) failed, throwing"); close(fd); jniThrowIOException(env, errno); return; } } } if (type == TYPE_RFCOMM) { sndbuf = RFCOMM_SO_SNDBUF; if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) { LOGV("setsockopt(SO_SNDBUF) failed, throwing"); close(fd); jniThrowIOException(env, errno); return; } } /* Setting L2CAP socket options */ if (type == TYPE_L2CAP || type == TYPE_EL2CAP) { struct l2cap_options opts; int optlen = sizeof(opts), err; err = getsockopt(fd, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen ); if (!err) { /* setting MTU for [E]L2CAP */ opts.omtu = opts.imtu = L2CAP_MAX_MTU; /* Enable ERTM for [E]L2CAP */ if (type == TYPE_EL2CAP) { opts.flush_to = 0xffff; /* infinite */ opts.mode = L2CAP_MODE_ERTM; opts.fcs = 1; opts.txwin_size = 64; opts.max_tx = 10; } err = setsockopt( fd, SOL_L2CAP, L2CAP_OPTIONS, &opts, optlen ); } /* Set larger SNDBUF & RCVBUF for EL2CAP connections */ if (type == TYPE_EL2CAP) { sndbuf = L2CAP_SO_SNDBUF; if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) { LOGV("setsockopt(SO_SNDBUF) failed, throwing"); close(fd); jniThrowIOException(env, errno); return; } rcvbuf = L2CAP_SO_RCVBUF; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))) { LOGV("setsockopt(SO_RCVBUF) failed, throwing"); close(fd); jniThrowIOException(env, errno); return; } } } LOGV("...fd %d created (%s, lm = %x)", fd, TYPE_AS_STR(type), lm); initSocketFromFdNative(env, obj, fd); return; #endif jniThrowIOException(env, ENOSYS); } static void setAmpPolicyNative(JNIEnv *env, jobject obj, jint amppol) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); struct asocket *s = get_socketData(env, obj); if (!s) return; int err; err = setsockopt(s->fd, SOL_BLUETOOTH, BT_AMP_POLICY, &pol, sizeof(amppol)); if (err) { LOGV("setsockopt() failed, throwing"); jniThrowIOException(env, errno); return; } return; #endif jniThrowIOException(env, ENOSYS); } static int getMtuNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); jint type = env->GetIntField(obj, field_mType); struct asocket *s = get_socketData(env, obj); if (type == TYPE_RFCOMM || !s) return L2CAP_MAX_MTU; struct l2cap_options opts; int optlen = sizeof(opts), err; err = getsockopt(s->fd, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen ); if (!err) { return opts.omtu; } else { LOGV("getsockopt() failed, throwing"); jniThrowIOException(env, errno); } return L2CAP_MAX_MTU; #endif jniThrowIOException(env, ENOSYS); return -1; } static void connectNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int ret; jint type; const char *c_address; jstring address; bdaddr_t bdaddress; socklen_t addr_sz; struct sockaddr *addr; struct asocket *s = get_socketData(env, obj); if (!s) return; type = env->GetIntField(obj, field_mType); /* parse address into bdaddress */ address = (jstring) env->GetObjectField(obj, field_mAddress); c_address = env->GetStringUTFChars(address, NULL); if (get_bdaddr(c_address, &bdaddress)) { env->ReleaseStringUTFChars(address, c_address); jniThrowIOException(env, EINVAL); return; } env->ReleaseStringUTFChars(address, c_address); switch (type) { case TYPE_RFCOMM: struct sockaddr_rc addr_rc; addr = (struct sockaddr *)&addr_rc; addr_sz = sizeof(addr_rc); memset(addr, 0, addr_sz); addr_rc.rc_family = AF_BLUETOOTH; addr_rc.rc_channel = env->GetIntField(obj, field_mPort); memcpy(&addr_rc.rc_bdaddr, &bdaddress, sizeof(bdaddr_t)); break; case TYPE_SCO: struct sockaddr_sco addr_sco; addr = (struct sockaddr *)&addr_sco; addr_sz = sizeof(addr_sco); memset(addr, 0, addr_sz); addr_sco.sco_family = AF_BLUETOOTH; memcpy(&addr_sco.sco_bdaddr, &bdaddress, sizeof(bdaddr_t)); break; case TYPE_L2CAP: case TYPE_EL2CAP: struct sockaddr_l2 addr_l2; addr = (struct sockaddr *)&addr_l2; addr_sz = sizeof(addr_l2); memset(addr, 0, addr_sz); addr_l2.l2_family = AF_BLUETOOTH; addr_l2.l2_psm = env->GetIntField(obj, field_mPort); memcpy(&addr_l2.l2_bdaddr, &bdaddress, sizeof(bdaddr_t)); break; default: jniThrowIOException(env, ENOSYS); return; } ret = asocket_connect(s, addr, addr_sz, -1); LOGV("...connect(%d, %s) = %d (errno %d)", s->fd, TYPE_AS_STR(type), ret, errno); if (ret) jniThrowIOException(env, errno); return; #endif jniThrowIOException(env, ENOSYS); } /* Returns errno instead of throwing, so java can check errno */ static int bindListenNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); jint type; socklen_t addr_sz; struct sockaddr *addr; bdaddr_t bdaddr = *BDADDR_ANY; struct asocket *s = get_socketData(env, obj); if (!s) return EINVAL; type = env->GetIntField(obj, field_mType); switch (type) { case TYPE_RFCOMM: struct sockaddr_rc addr_rc; addr = (struct sockaddr *)&addr_rc; addr_sz = sizeof(addr_rc); memset(addr, 0, addr_sz); addr_rc.rc_family = AF_BLUETOOTH; addr_rc.rc_channel = env->GetIntField(obj, field_mPort); memcpy(&addr_rc.rc_bdaddr, &bdaddr, sizeof(bdaddr_t)); break; case TYPE_SCO: struct sockaddr_sco addr_sco; addr = (struct sockaddr *)&addr_sco; addr_sz = sizeof(addr_sco); memset(addr, 0, addr_sz); addr_sco.sco_family = AF_BLUETOOTH; memcpy(&addr_sco.sco_bdaddr, &bdaddr, sizeof(bdaddr_t)); break; case TYPE_L2CAP: case TYPE_EL2CAP: struct sockaddr_l2 addr_l2; addr = (struct sockaddr *)&addr_l2; addr_sz = sizeof(addr_l2); memset(addr, 0, addr_sz); addr_l2.l2_family = AF_BLUETOOTH; addr_l2.l2_psm = env->GetIntField(obj, field_mPort); memcpy(&addr_l2.l2_bdaddr, &bdaddr, sizeof(bdaddr_t)); break; default: return ENOSYS; } if (bind(s->fd, addr, addr_sz)) { LOGV("...bind(%d) gave errno %d", s->fd, errno); return errno; } if (listen(s->fd, 1)) { LOGV("...listen(%d) gave errno %d", s->fd, errno); return errno; } LOGV("...bindListenNative(%d) success", s->fd); return 0; #endif return ENOSYS; } static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int fd; jint type; struct sockaddr *addr; socklen_t addr_sz; jstring addr_jstr; char addr_cstr[BTADDR_SIZE]; bdaddr_t *bdaddr; jboolean auth; jboolean encrypt; struct asocket *s = get_socketData(env, obj); if (!s) return NULL; type = env->GetIntField(obj, field_mType); switch (type) { case TYPE_RFCOMM: struct sockaddr_rc addr_rc; addr = (struct sockaddr *)&addr_rc; addr_sz = sizeof(addr_rc); bdaddr = &addr_rc.rc_bdaddr; memset(addr, 0, addr_sz); break; case TYPE_SCO: struct sockaddr_sco addr_sco; addr = (struct sockaddr *)&addr_sco; addr_sz = sizeof(addr_sco); bdaddr = &addr_sco.sco_bdaddr; memset(addr, 0, addr_sz); break; case TYPE_L2CAP: case TYPE_EL2CAP: struct sockaddr_l2 addr_l2; addr = (struct sockaddr *)&addr_l2; addr_sz = sizeof(addr_l2); bdaddr = &addr_l2.l2_bdaddr; memset(addr, 0, addr_sz); break; default: jniThrowIOException(env, ENOSYS); return NULL; } fd = asocket_accept(s, addr, &addr_sz, timeout); LOGV("...accept(%d, %s) = %d (errno %d)", s->fd, TYPE_AS_STR(type), fd, errno); if (fd < 0) { jniThrowIOException(env, errno); return NULL; } /* Connected - return new BluetoothSocket */ auth = env->GetBooleanField(obj, field_mAuth); encrypt = env->GetBooleanField(obj, field_mEncrypt); get_bdaddr_as_string(bdaddr, addr_cstr); addr_jstr = env->NewStringUTF(addr_cstr); return env->NewObject(class_BluetoothSocket, method_BluetoothSocket_ctor, type, fd, auth, encrypt, addr_jstr, -1); #endif jniThrowIOException(env, ENOSYS); return NULL; } static jint availableNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int available; struct asocket *s = get_socketData(env, obj); if (!s) return -1; if (ioctl(s->fd, FIONREAD, &available) < 0) { jniThrowIOException(env, errno); return -1; } return available; #endif jniThrowIOException(env, ENOSYS); return -1; } static jint readNative(JNIEnv *env, jobject obj, jbyteArray jb, jint offset, jint length) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int ret; jbyte *b; int sz; struct asocket *s = get_socketData(env, obj); if (!s) return -1; if (jb == NULL) { jniThrowIOException(env, EINVAL); return -1; } sz = env->GetArrayLength(jb); if (offset < 0 || length < 0 || offset + length > sz) { jniThrowIOException(env, EINVAL); return -1; } b = env->GetByteArrayElements(jb, NULL); if (b == NULL) { jniThrowIOException(env, EINVAL); return -1; } ret = asocket_read(s, &b[offset], length, -1); if (ret < 0) { jniThrowIOException(env, errno); env->ReleaseByteArrayElements(jb, b, JNI_ABORT); return -1; } env->ReleaseByteArrayElements(jb, b, 0); return (jint)ret; #endif jniThrowIOException(env, ENOSYS); return -1; } static jint writeNative(JNIEnv *env, jobject obj, jbyteArray jb, jint offset, jint length) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); int ret; jbyte *b; int sz; struct asocket *s = get_socketData(env, obj); if (!s) return -1; if (jb == NULL) { jniThrowIOException(env, EINVAL); return -1; } sz = env->GetArrayLength(jb); if (offset < 0 || length < 0 || offset + length > sz) { jniThrowIOException(env, EINVAL); return -1; } b = env->GetByteArrayElements(jb, NULL); if (b == NULL) { jniThrowIOException(env, EINVAL); return -1; } ret = asocket_write(s, &b[offset], length, -1); if (ret < 0) { jniThrowIOException(env, errno); env->ReleaseByteArrayElements(jb, b, JNI_ABORT); return -1; } env->ReleaseByteArrayElements(jb, b, JNI_ABORT); // no need to commit return (jint)ret; #endif jniThrowIOException(env, ENOSYS); return -1; } static void abortNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); struct asocket *s = get_socketData(env, obj); if (!s) return; asocket_abort(s); LOGV("...asocket_abort(%d) complete", s->fd); return; #endif jniThrowIOException(env, ENOSYS); } static void destroyNative(JNIEnv *env, jobject obj) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); struct asocket *s = get_socketData(env, obj); int fd = s->fd; if (!s) return; asocket_destroy(s); LOGV("...asocket_destroy(%d) complete", fd); return; #endif jniThrowIOException(env, ENOSYS); } static void throwErrnoNative(JNIEnv *env, jobject obj, jint err) { jniThrowIOException(env, err); } static JNINativeMethod sMethods[] = { {"initSocketNative", "()V", (void*) initSocketNative}, {"initSocketFromFdNative", "(I)V", (void*) initSocketFromFdNative}, {"setAmpPolicyNative", "(I)V", (void*) setAmpPolicyNative}, {"getMtuNative", "()I", (void*) getMtuNative}, {"connectNative", "()V", (void *) connectNative}, {"bindListenNative", "()I", (void *) bindListenNative}, {"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative}, {"availableNative", "()I", (void *) availableNative}, {"readNative", "([BII)I", (void *) readNative}, {"writeNative", "([BII)I", (void *) writeNative}, {"abortNative", "()V", (void *) abortNative}, {"destroyNative", "()V", (void *) destroyNative}, {"throwErrnoNative", "(I)V", (void *) throwErrnoNative}, }; int register_android_bluetooth_BluetoothSocket(JNIEnv *env) { jclass clazz = env->FindClass("android/bluetooth/BluetoothSocket"); if (clazz == NULL) return -1; class_BluetoothSocket = (jclass) env->NewGlobalRef(clazz); field_mType = env->GetFieldID(clazz, "mType", "I"); field_mAddress = env->GetFieldID(clazz, "mAddress", "Ljava/lang/String;"); field_mPort = env->GetFieldID(clazz, "mPort", "I"); field_mAuth = env->GetFieldID(clazz, "mAuth", "Z"); field_mEncrypt = env->GetFieldID(clazz, "mEncrypt", "Z"); field_mSocketData = env->GetFieldID(clazz, "mSocketData", "I"); method_BluetoothSocket_ctor = env->GetMethodID(clazz, "", "(IIZZLjava/lang/String;I)V"); return AndroidRuntime::registerNativeMethods(env, "android/bluetooth/BluetoothSocket", sMethods, NELEM(sMethods)); } } /* namespace android */