1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.security;
18
19import android.net.LocalSocketAddress;
20import android.net.LocalSocket;
21import android.util.Config;
22import android.util.Log;
23
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.OutputStream;
27import java.net.Socket;
28
29/*
30 * ServiceCommand is used to connect to a service throught the local socket,
31 * and send out the command, return the result to the caller.
32 * {@hide}
33 */
34public class ServiceCommand {
35    public static final String SUCCESS = "0";
36    public static final String FAILED = "-1";
37
38    // Opcodes for keystore commands.
39    public static final int LOCK = 0;
40    public static final int UNLOCK = 1;
41    public static final int PASSWD = 2;
42    public static final int GET_STATE = 3;
43    public static final int LIST_KEYS = 4;
44    public static final int GET_KEY = 5;
45    public static final int PUT_KEY = 6;
46    public static final int REMOVE_KEY = 7;
47    public static final int RESET = 8;
48    public static final int MAX_CMD_INDEX = 9;
49
50    public static final int BUFFER_LENGTH = 4096;
51
52    private static final boolean DBG = true;
53
54    private String mServiceName;
55    private String mTag;
56    private InputStream mIn;
57    private OutputStream mOut;
58    private LocalSocket mSocket;
59
60    private boolean connect() {
61        if (mSocket != null) {
62            return true;
63        }
64        if (DBG) Log.d(mTag, "connecting...");
65        try {
66            mSocket = new LocalSocket();
67
68            LocalSocketAddress address = new LocalSocketAddress(
69                    mServiceName, LocalSocketAddress.Namespace.RESERVED);
70
71            mSocket.connect(address);
72
73            mIn = mSocket.getInputStream();
74            mOut = mSocket.getOutputStream();
75        } catch (IOException ex) {
76            disconnect();
77            return false;
78        }
79        return true;
80    }
81
82    private void disconnect() {
83        if (DBG) Log.d(mTag,"disconnecting...");
84        try {
85            if (mSocket != null) mSocket.close();
86        } catch (IOException ex) { }
87        try {
88            if (mIn != null) mIn.close();
89        } catch (IOException ex) { }
90        try {
91            if (mOut != null) mOut.close();
92        } catch (IOException ex) { }
93        mSocket = null;
94        mIn = null;
95        mOut = null;
96    }
97
98    private boolean readBytes(byte buffer[], int len) {
99        int off = 0, count;
100        if (len < 0) return false;
101        while (off != len) {
102            try {
103                count = mIn.read(buffer, off, len - off);
104                if (count <= 0) {
105                    Log.e(mTag, "read error " + count);
106                    break;
107                }
108                off += count;
109            } catch (IOException ex) {
110                Log.e(mTag,"read exception", ex);
111                break;
112            }
113        }
114        if (off == len) return true;
115        disconnect();
116        return false;
117    }
118
119    private Reply readReply() {
120        byte buf[] = new byte[4];
121        Reply reply = new Reply();
122
123        if (!readBytes(buf, 4)) return null;
124        reply.len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8) |
125                ((((int) buf[2]) & 0xff) << 16) |
126                ((((int) buf[3]) & 0xff) << 24);
127
128        if (!readBytes(buf, 4)) return null;
129        reply.returnCode = (((int) buf[0]) & 0xff) |
130                ((((int) buf[1]) & 0xff) << 8) |
131                ((((int) buf[2]) & 0xff) << 16) |
132                ((((int) buf[3]) & 0xff) << 24);
133
134        if (reply.len > BUFFER_LENGTH) {
135            Log.e(mTag,"invalid reply length (" + reply.len + ")");
136            disconnect();
137            return null;
138        }
139        if (!readBytes(reply.data, reply.len)) return null;
140        return reply;
141    }
142
143    private boolean writeCommand(int cmd, String _data) {
144        byte buf[] = new byte[8];
145        byte[] data = (_data == null) ? new byte[0] : _data.getBytes();
146        int len = data.length;
147        // the length of data
148        buf[0] = (byte) (len & 0xff);
149        buf[1] = (byte) ((len >> 8) & 0xff);
150        buf[2] = (byte) ((len >> 16) & 0xff);
151        buf[3] = (byte) ((len >> 24) & 0xff);
152        // the opcode of the command
153        buf[4] = (byte) (cmd & 0xff);
154        buf[5] = (byte) ((cmd >> 8) & 0xff);
155        buf[6] = (byte) ((cmd >> 16) & 0xff);
156        buf[7] = (byte) ((cmd >> 24) & 0xff);
157        try {
158            mOut.write(buf, 0, 8);
159            mOut.write(data, 0, len);
160        } catch (IOException ex) {
161            Log.e(mTag,"write error", ex);
162            disconnect();
163            return false;
164        }
165        return true;
166    }
167
168    private Reply executeCommand(int cmd, String data) {
169        if (!writeCommand(cmd, data)) {
170            /* If service died and restarted in the background
171             * (unlikely but possible) we'll fail on the next
172             * write (this one).  Try to reconnect and write
173             * the command one more time before giving up.
174             */
175            Log.e(mTag, "write command failed? reconnect!");
176            if (!connect() || !writeCommand(cmd, data)) {
177                return null;
178            }
179        }
180        return readReply();
181    }
182
183    public synchronized Reply execute(int cmd, String data) {
184      Reply result;
185      if (!connect()) {
186          Log.e(mTag, "connection failed");
187          return null;
188      }
189      result = executeCommand(cmd, data);
190      disconnect();
191      return result;
192    }
193
194    public ServiceCommand(String service) {
195        mServiceName = service;
196        mTag = service;
197    }
198}
199