/* * Copyright (C) 2007 The Android Open Source Project * * 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 android.net; import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; import java.io.FileDescriptor; import java.net.SocketOptions; /** * Socket implementation used for android.net.LocalSocket and * android.net.LocalServerSocket. Supports only AF_LOCAL sockets. */ class LocalSocketImpl { private SocketInputStream fis; private SocketOutputStream fos; private Object readMonitor = new Object(); private Object writeMonitor = new Object(); /** null if closed or not yet created */ private FileDescriptor fd; // These fields are accessed by native code; /** file descriptor array received during a previous read */ FileDescriptor[] inboundFileDescriptors; /** file descriptor array that should be written during next write */ FileDescriptor[] outboundFileDescriptors; /** * An input stream for local sockets. Needed because we may * need to read ancillary data. */ class SocketInputStream extends InputStream { /** {@inheritDoc} */ @Override public int available() throws IOException { return available_native(fd); } /** {@inheritDoc} */ @Override public void close() throws IOException { LocalSocketImpl.this.close(); } /** {@inheritDoc} */ @Override public int read() throws IOException { int ret; synchronized (readMonitor) { FileDescriptor myFd = fd; if (myFd == null) throw new IOException("socket closed"); ret = read_native(myFd); return ret; } } /** {@inheritDoc} */ @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { synchronized (readMonitor) { FileDescriptor myFd = fd; if (myFd == null) throw new IOException("socket closed"); if (off < 0 || len < 0 || (off + len) > b.length ) { throw new ArrayIndexOutOfBoundsException(); } int ret = readba_native(b, off, len, myFd); return ret; } } } /** * An output stream for local sockets. Needed because we may * need to read ancillary data. */ class SocketOutputStream extends OutputStream { /** {@inheritDoc} */ @Override public void close() throws IOException { LocalSocketImpl.this.close(); } /** {@inheritDoc} */ @Override public void write (byte[] b) throws IOException { write(b, 0, b.length); } /** {@inheritDoc} */ @Override public void write (byte[] b, int off, int len) throws IOException { synchronized (writeMonitor) { FileDescriptor myFd = fd; if (myFd == null) throw new IOException("socket closed"); if (off < 0 || len < 0 || (off + len) > b.length ) { throw new ArrayIndexOutOfBoundsException(); } writeba_native(b, off, len, myFd); } } /** {@inheritDoc} */ @Override public void write (int b) throws IOException { synchronized (writeMonitor) { FileDescriptor myFd = fd; if (myFd == null) throw new IOException("socket closed"); write_native(b, myFd); } } } private native int available_native(FileDescriptor fd) throws IOException; private native void close_native(FileDescriptor fd) throws IOException; private native int read_native(FileDescriptor fd) throws IOException; private native int readba_native(byte[] b, int off, int len, FileDescriptor fd) throws IOException; private native void writeba_native(byte[] b, int off, int len, FileDescriptor fd) throws IOException; private native void write_native(int b, FileDescriptor fd) throws IOException; private native void connectLocal(FileDescriptor fd, String name, int namespace) throws IOException; private native void bindLocal(FileDescriptor fd, String name, int namespace) throws IOException; private native FileDescriptor create_native(boolean stream) throws IOException; private native void listen_native(FileDescriptor fd, int backlog) throws IOException; private native void shutdown(FileDescriptor fd, boolean shutdownInput); private native Credentials getPeerCredentials_native( FileDescriptor fd) throws IOException; private native int getOption_native(FileDescriptor fd, int optID) throws IOException; private native void setOption_native(FileDescriptor fd, int optID, int b, int value) throws IOException; // private native LocalSocketAddress getSockName_native // (FileDescriptor fd) throws IOException; /** * Accepts a connection on a server socket. * * @param fd file descriptor of server socket * @param s socket implementation that will become the new socket * @return file descriptor of new socket */ private native FileDescriptor accept (FileDescriptor fd, LocalSocketImpl s) throws IOException; /** * Create a new instance. */ /*package*/ LocalSocketImpl() { } /** * Create a new instance from a file descriptor representing * a bound socket. The state of the file descriptor is not checked here * but the caller can verify socket state by calling listen(). * * @param fd non-null; bound file descriptor */ /*package*/ LocalSocketImpl(FileDescriptor fd) throws IOException { this.fd = fd; } public String toString() { return super.toString() + " fd:" + fd; } /** * Creates a socket in the underlying OS. * * @param stream true if this should be a stream socket, false for * datagram. * @throws IOException */ public void create (boolean stream) throws IOException { // no error if socket already created // need this for LocalServerSocket.accept() if (fd == null) { fd = create_native(stream); } } /** * Closes the socket. * * @throws IOException */ public void close() throws IOException { synchronized (LocalSocketImpl.this) { if (fd == null) return; close_native(fd); fd = null; } } /** note timeout presently ignored */ protected void connect(LocalSocketAddress address, int timeout) throws IOException { if (fd == null) { throw new IOException("socket not created"); } connectLocal(fd, address.getName(), address.getNamespace().getId()); } /** * Binds this socket to an endpoint name. May only be called on an instance * that has not yet been bound. * * @param endpoint endpoint address * @throws IOException */ public void bind(LocalSocketAddress endpoint) throws IOException { if (fd == null) { throw new IOException("socket not created"); } bindLocal(fd, endpoint.getName(), endpoint.getNamespace().getId()); } protected void listen(int backlog) throws IOException { if (fd == null) { throw new IOException("socket not created"); } listen_native(fd, backlog); } /** * Accepts a new connection to the socket. Blocks until a new * connection arrives. * * @param s a socket that will be used to represent the new connection. * @throws IOException */ protected void accept(LocalSocketImpl s) throws IOException { if (fd == null) { throw new IOException("socket not created"); } s.fd = accept(fd, s); } /** * Retrieves the input stream for this instance. * * @return input stream * @throws IOException if socket has been closed or cannot be created. */ protected InputStream getInputStream() throws IOException { if (fd == null) { throw new IOException("socket not created"); } synchronized (this) { if (fis == null) { fis = new SocketInputStream(); } return fis; } } /** * Retrieves the output stream for this instance. * * @return output stream * @throws IOException if socket has been closed or cannot be created. */ protected OutputStream getOutputStream() throws IOException { if (fd == null) { throw new IOException("socket not created"); } synchronized (this) { if (fos == null) { fos = new SocketOutputStream(); } return fos; } } /** * Returns the number of bytes available for reading without blocking. * * @return >= 0 count bytes available * @throws IOException */ protected int available() throws IOException { return getInputStream().available(); } /** * Shuts down the input side of the socket. * * @throws IOException */ protected void shutdownInput() throws IOException { if (fd == null) { throw new IOException("socket not created"); } shutdown(fd, true); } /** * Shuts down the output side of the socket. * * @throws IOException */ protected void shutdownOutput() throws IOException { if (fd == null) { throw new IOException("socket not created"); } shutdown(fd, false); } protected FileDescriptor getFileDescriptor() { return fd; } protected boolean supportsUrgentData() { return false; } protected void sendUrgentData(int data) throws IOException { throw new RuntimeException ("not impled"); } public Object getOption(int optID) throws IOException { if (fd == null) { throw new IOException("socket not created"); } if (optID == SocketOptions.SO_TIMEOUT) { return 0; } int value = getOption_native(fd, optID); switch (optID) { case SocketOptions.SO_RCVBUF: case SocketOptions.SO_SNDBUF: return value; case SocketOptions.SO_REUSEADDR: default: return value; } } public void setOption(int optID, Object value) throws IOException { /* * Boolean.FALSE is used to disable some options, so it * is important to distinguish between FALSE and unset. * We define it here that -1 is unset, 0 is FALSE, and 1 * is TRUE. */ int boolValue = -1; int intValue = 0; if (fd == null) { throw new IOException("socket not created"); } if (value instanceof Integer) { intValue = (Integer)value; } else if (value instanceof Boolean) { boolValue = ((Boolean) value)? 1 : 0; } else { throw new IOException("bad value: " + value); } setOption_native(fd, optID, boolValue, intValue); } /** * Enqueues a set of file descriptors to send to the peer. The queue * is one deep. The file descriptors will be sent with the next write * of normal data, and will be delivered in a single ancillary message. * See "man 7 unix" SCM_RIGHTS on a desktop Linux machine. * * @param fds non-null; file descriptors to send. * @throws IOException */ public void setFileDescriptorsForSend(FileDescriptor[] fds) { synchronized(writeMonitor) { outboundFileDescriptors = fds; } } /** * Retrieves a set of file descriptors that a peer has sent through * an ancillary message. This method retrieves the most recent set sent, * and then returns null until a new set arrives. * File descriptors may only be passed along with regular data, so this * method can only return a non-null after a read operation. * * @return null or file descriptor array * @throws IOException */ public FileDescriptor[] getAncillaryFileDescriptors() throws IOException { synchronized(readMonitor) { FileDescriptor[] result = inboundFileDescriptors; inboundFileDescriptors = null; return result; } } /** * Retrieves the credentials of this socket's peer. Only valid on * connected sockets. * * @return non-null; peer credentials * @throws IOException */ public Credentials getPeerCredentials() throws IOException { return getPeerCredentials_native(fd); } /** * Retrieves the socket name from the OS. * * @return non-null; socket name * @throws IOException on failure */ public LocalSocketAddress getSockAddress() throws IOException { return null; //TODO implement this //return getSockName_native(fd); } @Override protected void finalize() throws IOException { close(); } }