/* * Copyright (C) 2014 Alexey Illarionov * * 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. */ package com.secupwn.aimsicd.rilexecutor; import android.annotation.SuppressLint; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Message; import android.os.Parcel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import io.freefair.android.util.logging.AndroidLogger; import io.freefair.android.util.logging.Logger; public class SamsungMulticlientRilExecutor implements OemRilExecutor { private final Logger log = AndroidLogger.forClass(SamsungMulticlientRilExecutor.class); private static final String MULTICLIENT_SOCKET = "Multiclient"; private static final int RIL_REQUEST_OEM_RAW = 59; private static final int RIL_REQUEST_OEM_STRINGS = 60; private static final int RIL_CLIENT_ERR_SUCCESS = 0; private static final int RESPONSE_SOLICITED = 0; private static final int RESPONSE_UNSOLICITED = 1; private static final int ID_REQUEST_AT_COMMAND = 5; private static final int ID_RESPONSE_AT_COMMAND = 104; // If you need debugging on, for testing on toher devices, change here. // WARNING: Could fill your logcat if running app long! // private static final boolean DBG = BuildConfig.DEBUG; private static final boolean DBG = false; private volatile LocalSocketThread mThread; @Override public DetectResult detect() { String gsmVerRilImpl = ""; try { Class clazz; clazz = Class.forName("android.os.SystemProperties"); Method method = clazz.getDeclaredMethod("get", String.class, String.class); gsmVerRilImpl = (String) method.invoke(null, "gsm.version.ril-impl", ""); } catch (Exception ignore) { log.debug("ignore this exception?", ignore); } // E:V:A comment out for debugging purposes on other non-Samsung RILS // and moved gsm.version.. to catch... // WARNING may have bad consequences... if (!gsmVerRilImpl.matches("Samsung\\s+RIL\\(IPC\\).*")) { return DetectResult.Unavailable("gsm.version.ril-impl = " + gsmVerRilImpl); } LocalSocket s = new LocalSocket(); try { s.connect(new LocalSocketAddress(MULTICLIENT_SOCKET)); } catch (IOException e) { log.warn(e.getMessage()); return DetectResult.Unavailable( "Multiclient socket is not available\n" + "gsm.version.ril-impl = " + gsmVerRilImpl); } finally { try { s.close(); } catch (IOException e) { log.error(e.getMessage(), e); } } return DetectResult.AVAILABLE; } @Override public synchronized void start() { if (mThread != null) { log.error("OEM raw request executor thread is running"); return; } mThread = new LocalSocketThread(MULTICLIENT_SOCKET); mThread.start(); } @Override public synchronized void stop() { if (mThread == null) { log.error("OEM raw request executor thread is not running"); return; } mThread.cancel(); mThread = null; } @Override public synchronized void invokeOemRilRequestRaw(byte[] data, Message response) { if (mThread == null) { log.error(" OEM raw request executor thread is not running"); return; } try { mThread.invokeOemRilRequestRaw(data, response); } catch (IOException ioe) { log.error("InvokeOemRilRequestRaw() error", ioe); } } @Override public synchronized void invokeOemRilRequestStrings(String[] strings, Message response) { if (mThread == null) { log.error("OEM raw request executor thread is not running"); return; } try { mThread.invokeOemRilRequestStrings(strings, response); } catch (IOException ioe) { log.error("InvokeOemRilRequestStrings() error", ioe); } } public class LocalSocketThread extends Thread { private static final int MAX_MESSAGES = 30; private final LocalSocketAddress mSocketPath; private final AtomicBoolean mCancelRequested = new AtomicBoolean(); private LocalSocket mSocket; private volatile InputStream mInputStream; private volatile OutputStream mOutputStream; private final Random mTokenGen = new Random(); private final Map<Integer, Message> mMessages; @SuppressLint("UseSparseArrays") public LocalSocketThread(String socketPath) { mSocketPath = new LocalSocketAddress(socketPath); mInputStream = null; mOutputStream = null; mMessages = new HashMap<>(); } public void cancel() { if (DBG) { log.verbose("SamsungMulticlientRil cancel()"); } synchronized (this) { mCancelRequested.set(true); disconnect(); notifyAll(); } } public synchronized void invokeOemRilRequestRaw(byte[] data, Message response) throws IOException { int token; if (mMessages.size() > MAX_MESSAGES) { log.error("message queue is full"); return; } if (mOutputStream == null) { log.error("Local write() error: not connected"); return; } do { token = mTokenGen.nextInt(); } while (mMessages.containsKey(token)); byte req[] = marshallRequest(token, data); if (DBG) { log.verbose(String.format("InvokeOemRilRequestRaw() token: 0x%X, header: %s, req: %s ", token, HexDump.toHexString(getHeader(req)), HexDump.toHexString(req)) ); } mOutputStream.write(getHeader(req)); mOutputStream.write(req); mMessages.put(token, response); } public synchronized void invokeOemRilRequestStrings(String strings[], Message response) throws IOException { int token; if (mMessages.size() > MAX_MESSAGES) { log.error("Message queue is full"); return; } if (mOutputStream == null) { log.error("Local write() error: not connected"); return; } do { token = mTokenGen.nextInt(); } while (mMessages.containsKey(token)); byte[] req = marshallRequest(token, strings); if (DBG) { log.verbose(String.format("InvokeOemRilRequestStrings() token: 0x%X, header: %s, req: %s ", token, HexDump.toHexString(getHeader(req)), HexDump.toHexString(req))); } mOutputStream.write(getHeader(req)); mOutputStream.write(req); mMessages.put(token, response); } private byte[] getHeader(byte data[]) { int len = data.length; return new byte[] { (byte) ((len >> 24) & 0xff), (byte) ((len >> 16) & 0xff), (byte) ((len >> 8) & 0xff), (byte) (len & 0xff) }; } private byte[] marshallRequest(int token, byte data[]) { Parcel p = Parcel.obtain(); p.writeInt(RIL_REQUEST_OEM_RAW); p.writeInt(token); p.writeByteArray(data); byte[] res = p.marshall(); p.recycle(); return res; } private byte[] marshallRequest(int token, String strings[]) { Parcel p = Parcel.obtain(); p.writeInt(RIL_REQUEST_OEM_STRINGS); p.writeInt(token); p.writeStringArray(strings); byte[] res = p.marshall(); p.recycle(); return res; } public synchronized void disconnect() { if (DBG) { log.verbose("Local disconnect()"); } if (mSocket == null) { return; } try { mSocket.shutdownInput(); } catch (IOException e) { log.error("Local shutdownInput() of mSocket failed", e); } try { mSocket.shutdownOutput(); } catch (IOException e) { log.error("Local shutdownOutput() of mSocket failed", e); } try { mInputStream.close(); } catch (IOException e) { log.error("Local close() of mInputStream failed", e); } try { mOutputStream.close(); } catch (IOException e) { log.error("Local close() of mOutputStream failed", e); } try { mSocket.close(); } catch (IOException e) { log.error("Local close() of mSocket failed", e); } mSocket = null; mInputStream = null; mOutputStream = null; System.gc(); } @Override public void run() { int rcvd; int endpos = 0; final byte buf[] = new byte[4096]; log.info("BEGIN LocalSocketThread-Socket"); setName("MultiClientThread"); mSocket = new LocalSocket(); try { mSocket.connect(mSocketPath); mInputStream = mSocket.getInputStream(); mOutputStream = mSocket.getOutputStream(); } catch (IOException e) { log.error("Connect error", e); return; } while (!mCancelRequested.get()) { try { rcvd = mInputStream.read(buf, endpos, buf.length - endpos); if (rcvd < 0) { if (DBG) { log.verbose("EOF reached"); } break; } endpos += rcvd; if (endpos < 4) { continue; } int msgLen = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | (buf[3] & 0xff); if (msgLen + 4 > buf.length) { log.error("Message to big. Length: " + msgLen); endpos = 0; continue; } if (endpos >= msgLen + 4) { processRxPacket(buf, 4, msgLen); int secondPktPos = msgLen + 4; if (secondPktPos != endpos) { System.arraycopy(buf, secondPktPos, buf, 0, endpos - secondPktPos); } endpos -= msgLen + 4; } if (endpos == buf.length) { endpos = 0; } } catch (IOException e) { disconnect(); } } disconnect(); } private synchronized void processRxPacket(byte data[], int pos, int length) { int responseType; Parcel p; if (DBG) { log.verbose("Received " + length + " bytes: " + HexDump.toHexString(data, pos, length)); } p = Parcel.obtain(); try { p.unmarshall(data, pos, length); p.setDataPosition(0); responseType = p.readInt(); switch (responseType) { case RESPONSE_UNSOLICITED: log.verbose("Unsolicited response "); break; case RESPONSE_SOLICITED: processSolicited(p); break; default: log.verbose("Invalid response type: " + responseType); break; } } finally { p.recycle(); } } private int processSolicited(Parcel p) { Integer token = null; byte responseData[] = null; String stringsResponseData[] = null; Exception errorEx = null; try { token = p.readInt(); int err = p.readInt(); if (DBG) { log.verbose(String.format(": processSolicited() token: 0x%X err: %d", token, err)); } if (err != RIL_CLIENT_ERR_SUCCESS) { throw new RemoteException("remote error " + err); } responseData = p.createByteArray(); stringsResponseData = p.createStringArray(); } catch (Exception ex) { log.error(ex.getMessage()); errorEx = ex; } if (token == null) { log.error("token is null", errorEx); } else { synchronized (this) { Message m = mMessages.remove(token); if (m != null) { switch (m.what) { case ID_REQUEST_AT_COMMAND: case ID_RESPONSE_AT_COMMAND: case RIL_REQUEST_OEM_STRINGS: m.obj = new StringsResult(stringsResponseData, errorEx); m.sendToTarget(); break; default: m.obj = new RawResult(responseData, errorEx); m.sendToTarget(); } } else { log.info("Message with token " + token + " not found"); } } } return RIL_CLIENT_ERR_SUCCESS; } } public static class RemoteException extends Exception { public RemoteException(String detailMessage) { super(detailMessage); } } }