Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create an in-memory FileDescriptor

FileDescriptor API in Android says:

Instances of the file descriptor class serve as an opaque handle to the underlying machine-specific structure representing an open file, an open socket, or another source or sink of bytes.

I want to create a FileDescriptor object using ByteArrayOutputStream and ByteArrayInputStream

Also, FileDescriptor is a final class and cannot be overridden. The only constructor it has says -

Constructs an (invalid) FileDescriptor object.

Any idea how to use FileDescriptor in Android?

EDIT

I want to use it in MediaMuxer. Instead of writing to a file, I want to have the media data in-memory and copy it to a TCP socket for live streaming. So my FileDescriptor should be a "sink of bytes."

like image 342
PC. Avatar asked Oct 24 '17 18:10

PC.


1 Answers

Use a LocalSocket, but be careful because there are security implications.

LocalSocket is the Android API for working with Unix Domain Sockets. Domain Sockets are similar to TCP/IP sockets except that they only exist on the device, they cannot be used for communication across a network.

A simple, but insecure solution to your problem is as follows:

// Create a unique name for the socket.
String name = "your.package.name-" + UUID.randomUUID();

// Bind a server to the socket.
final LocalServerSocket server = new LocalServerSocket(name);

// Connect a client to the socket.
LocalSocket client = new LocalSocket(LocalSocket.SOCKET_STREAM);
client.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));

// Start a thread to read from the server socket.
new Thread(new Runnable {
    @Override
    public void run() {
        LocalSocket socket = server.accept();

        // To read data sent by the client, read from socket.getInputStream().
        // To send data to the client, write to socket.getOutputStream().

        // After you are done you will need to call socket.close().
    }
}).start();

// Get the FileDescriptor associated with the client.
// You can use this FileDescriptor to write data to and/or read data
// sent from the server.
FileDescriptor fileDescriptor = client.getFileDescriptor();

// After you are done you will need to call server.close() and client.close().

This creates a socket in the abstract socket namespace. This is insecure because domain sockets are system-wide and sockets in the abstract namespace are not restricted by any permissions system. The only requirement to connect or bind to a name in the abstract namespace is to know the name, and the socket name used by your app can be easily discovered by an attacker through decompilation. There is also a possibility that another app may use the same socket name by accident. So another app could intercept your data, or send unexpected data through the socket, and it is difficult to guard against this.

A better, but more complicated solution is to create a socket in the filesystem namespace. The API to do this is super weird, but it can be achieved as follows:

// Create a unique name for the socket in your app's private data area.
// Note this example creates a file named like socket-xxxx in the root of
// your app's private data area. You might want to put it in a subdirectory.
String name = context
    .getFileStreamPath("socket-" + UUID.randomUUID())
    .getAbsolutePath();
LocalSocketAddress address = new LocalSocketAddress(name, LocalSocketAddress.Namespace.FILESYSTEM);

// Bind a server to the socket.
LocalSocket server = new LocalSocket(LocalSocket.SOCKET_STREAM);
server.bind(address);
final LocalServerSocket serverWrapper = new LocalServerSocket(server.getFileDescriptor());

// Connect a client to the socket.
LocalSocket client = new LocalSocket(LocalSocket.SOCKET_STREAM);
client.connect(address);

// Start a thread to read from the server socket.
new Thread(new Runnable {
    @Override
    public void run() {
        LocalSocket socket = serverWrapper.accept();

        // To read data sent by the client, read from socket.getInputStream().
        // To send data to the client, write to socket.getOutputStream().

        // After you are done you will need to call socket.close() and
        // serverWrapper.close().
    }
}).start();

// Get the FileDescriptor associated with the client.
// You can use this FileDescriptor to write data to and/or read data
// sent from the server.
FileDescriptor fileDescriptor = client.getFileDescriptor();

// After you are done you will need to call server.close() and client.close().

This does create a file on the filesystem, but none of the data that passes through the socket is ever written to disk, it is entirely in-memory. The file is just a name that represents the socket, similar to the files in /dev that represent devices. Because the socket is accessed through the filesystem, it is subject to the usual filesystem permissions, so it is easy to restrict access to the socket by placing the socket in your app's private data area.

Since this technique creates a file on the filesystem, it would be a good idea to delete the file after you're done, and also perhaps to check for and clean up old sockets every so often, in case your app crashes and leaves old files laying around.

like image 101
Daniel Cassidy Avatar answered Oct 02 '22 00:10

Daniel Cassidy