/*
* Copyright (C) 2013 RoboVM AB
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
*/
package org.robovm.libimobiledevice;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.robovm.libimobiledevice.binding.IDeviceConnectionRef;
import org.robovm.libimobiledevice.binding.IntOut;
import org.robovm.libimobiledevice.binding.LibIMobileDevice;
/**
* Represents a device connection.
*/
public class IDeviceConnection implements AutoCloseable {
protected IDeviceConnectionRef ref;
private DeviceInputStream deviceInputStream;
private DeviceOutputStream deviceOutputStream;
IDeviceConnection(IDeviceConnectionRef ref) {
this.ref = ref;
}
protected IDeviceConnectionRef getRef() {
checkDisposed();
return ref;
}
private void createStreams() {
if (deviceInputStream == null) {
deviceInputStream = new DeviceInputStream();
deviceOutputStream = new DeviceOutputStream();
}
}
/**
* Returns an {@link InputStream} for reading from this
* {@link IDeviceConnection}.
*/
public InputStream getInputStream() {
createStreams();
return deviceInputStream;
}
/**
* Returns and {@link OutputStream} for writing to this
* {@link IDeviceConnection}.
*/
public OutputStream getOutputStream() {
createStreams();
return deviceOutputStream;
}
/**
* Receives data from the device. Waits indefinitely for data on the
* connection.
*
* @param buffer the byte array in which to store the received data.
* @param offset the initial position in {@code buffer} to store the
* received bytes.
* @param count the maximum number of bytes to store in {@code buffer}.
* @return the number of bytes received.
*/
public int receive(byte[] buffer, int offset, int count) {
return receive(buffer, offset, count, Integer.MAX_VALUE);
}
/**
* Receives data from the device. Returns after the given timeout even if no
* data has been received.
*
* @param buffer the byte array in which to store the received data.
* @param offset the initial position in {@code buffer} to store the
* received bytes.
* @param count the maximum number of bytes to store in {@code buffer}.
* @param timeout timeout in milliseconds after which this method will
* return even if no data has been received.
* @return the number of bytes received.
*/
public int receive(byte[] buffer, int offset, int count, int timeout) {
checkArrayBounds(buffer, offset, count);
if (count == 0) {
return 0;
}
byte[] data = buffer;
if (offset > 0) {
data = new byte[count];
}
IntOut bytesReceivedOut = new IntOut();
try {
IDevice.checkResult(LibIMobileDevice.idevice_connection_receive_timeout(getRef(),
data, count, bytesReceivedOut, timeout));
int bytesRead = bytesReceivedOut.getValue();
if (bytesRead > 0 && data != buffer) {
System.arraycopy(data, 0, buffer, offset, bytesRead);
}
return bytesRead;
} finally {
bytesReceivedOut.delete();
}
}
private void checkArrayBounds(byte[] buffer, int offset, int count) {
if ((offset | count) < 0 || offset > buffer.length || buffer.length - offset < count) {
throw new ArrayIndexOutOfBoundsException("length=" + buffer.length
+ "; regionStart=" + offset + "; regionLength=" + count);
}
}
/**
* Sends data to the device on this connection.
*
* @param buffer the buffer to be sent.
* @param offset the start position in {@code buffer} from where to get bytes.
* @param count the number of bytes from {@code buffer} to send.
* @return the number of bytes actually sent.
*/
public int send(byte[] buffer, int offset, int count) {
checkArrayBounds(buffer, offset, count);
if (count == 0) {
return 0;
}
byte[] data = buffer;
if (offset > 0) {
data = new byte[count];
System.arraycopy(buffer, offset, data, 0, count);
}
IntOut bytesSentOut = new IntOut();
try {
IDevice.checkResult(LibIMobileDevice.idevice_connection_send(getRef(), data, count, bytesSentOut));
return bytesSentOut.getValue();
} finally {
bytesSentOut.delete();
}
}
/**
* Disconnects from the device. This is the same as calling {@link #dispose()}
* or {@link #close()}.
*/
public void disconnect() {
dispose();
}
protected final void checkDisposed() {
if (ref == null) {
throw new LibIMobileDeviceException("Already disposed");
}
}
public synchronized void dispose() {
checkDisposed();
LibIMobileDevice.idevice_disconnect(ref);
ref = null;
}
@Override
public void close() {
dispose();
}
private class DeviceInputStream extends InputStream {
@Override
public int read() throws IOException {
byte[] b = new byte[1];
int n = read(b);
return n <= 0 ? -1 : b[0] & 0xff;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
return receive(b, off, len);
} catch (LibIMobileDeviceException e) {
throw new IOException(e);
}
}
}
private class DeviceOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
byte[] buffer = new byte[1];
buffer[0] = (byte) b;
write(buffer);
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
checkArrayBounds(b, off, len);
try {
send(b, off, len);
} catch (LibIMobileDeviceException e) {
throw new IOException(e);
}
}
}
}