/*
* Copyright (C) 2015 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 com.android.tools.rpclib.multiplex;
import com.android.annotations.concurrency.GuardedBy;
import com.android.tools.rpclib.binary.Decoder;
import com.android.tools.rpclib.binary.Encoder;
import com.intellij.openapi.diagnostic.Logger;
import gnu.trove.TLongObjectHashMap;
import gnu.trove.TLongObjectIterator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;
public class Multiplexer {
@NotNull private static final Logger LOG = Logger.getInstance(Multiplexer.class);
private final Decoder mDecoder;
private final Encoder mEncoder;
private final NewChannelListener mNewChannelListener;
private final Channel.EventHandler mChannelEventHandler;
private final Sender mSender;
private final AtomicLong mNextChannelId;
@GuardedBy("mChannelMap") private final TLongObjectHashMap<Channel> mChannelMap;
public Multiplexer(@NotNull InputStream in, @NotNull OutputStream out, int mtu,
@NotNull ExecutorService executorService,
@Nullable NewChannelListener newChannelListener) {
mDecoder = new Decoder(in);
mEncoder = new Encoder(out);
mNewChannelListener = newChannelListener;
mChannelEventHandler = new ChannelEventHandler();
mSender = new Sender(mtu, executorService);
mChannelMap = new TLongObjectHashMap<Channel>();
mNextChannelId = new AtomicLong(0);
executorService.execute(new Receiver());
}
public Channel openChannel() throws IOException {
final long id = mNextChannelId.getAndIncrement();
Channel channel = newChannel(id);
mSender.sendOpenChannel(id);
return channel;
}
private Channel newChannel(final long id) throws IOException {
Channel channel = new Channel(id, mChannelEventHandler);
synchronized (mChannelMap) {
if (mChannelMap.isEmpty()) {
mSender.begin(mEncoder);
}
mChannelMap.put(id, channel);
}
return channel;
}
private void deleteChannel(long id) {
synchronized (mChannelMap) {
if (mChannelMap.containsKey(id)) {
// TODO: Mark channel closed.
mChannelMap.remove(id);
if (mChannelMap.isEmpty()) {
mSender.end();
}
}
else {
// This can happen when both ends close simultaneously.
LOG.info("Attempting to close unknown channel " + id);
}
}
}
private Channel getChannel(long id) {
Channel channel;
synchronized (mChannelMap) {
channel = mChannelMap.get(id);
}
return channel;
}
private void closeAllChannels() {
synchronized (mChannelMap) {
for (TLongObjectIterator<Channel> it = mChannelMap.iterator(); it.hasNext(); it.advance()) {
Channel c = it.value();
try {
c.close();
}
catch (IOException e) {
}
it.remove();
}
}
}
private class ChannelEventHandler implements Channel.EventHandler {
@Override
public void closeChannel(long id) throws IOException {
mSender.sendCloseChannel(id);
deleteChannel(id);
}
@Override
public void writeChannel(long id, byte[] b, int off, int len) throws IOException {
mSender.sendData(id, b, off, len);
}
}
private class Receiver extends Thread {
Receiver() {
super("rpclib.multiplex Receiver");
}
@Override
public void run() {
try {
while (true) {
short msgType = mDecoder.uint8();
long id = ~(mDecoder.uint32() & 0xffffffff);
switch (msgType) {
case Message.OPEN_CHANNEL: {
Channel channel = newChannel(id);
if (mNewChannelListener != null) {
mNewChannelListener.onNewChannel(channel);
}
break;
}
case Message.CLOSE_CHANNEL: {
Channel channel = getChannel(id);
if (channel != null) {
channel.closeNoEvent();
deleteChannel(id);
}
break;
}
case Message.DATA: {
int count = mDecoder.uint32();
byte[] buf = new byte[count];
for (int offset = 0; offset < count;) {
offset += mDecoder.stream().read(buf, offset, count-offset);
}
Channel channel = getChannel(id);
if (channel != null) {
channel.receive(buf);
}
else {
// Likely this channel was closed this side, and we're receiving data
// that should be dropped on the floor.
LOG.info("Received data on unknown channel " + id);
}
break;
}
default:
throw new UnsupportedOperationException("Unknown msgType: " + msgType);
}
}
}
catch (IOException e) {
LOG.info(e);
}
catch (UnsupportedOperationException e) {
LOG.error(e);
}
finally {
closeAllChannels();
}
}
}
}