/* * Copyright 2010 netling project <http://netling.org> * * 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. * * This file may incorporate work covered by the following copyright and * permission notice: * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.netling.ssh.connection.channel; import java.io.IOException; import java.io.OutputStream; import org.netling.ssh.common.ErrorNotifiable; import org.netling.ssh.common.Message; import org.netling.ssh.common.SSHException; import org.netling.ssh.common.SSHPacket; import org.netling.ssh.connection.ConnectionException; import org.netling.ssh.transport.Transport; /** * {@link OutputStream} for channels. Buffers data upto the remote window's maximum packet size. Data can also be * flushed via {@link #flush()} and is also flushed on {@link #close()}. */ public final class ChannelOutputStream extends OutputStream implements ErrorNotifiable { private final Channel chan; private final Transport trans; private final Window.Remote win; private final SSHPacket buffer = new SSHPacket(); private final byte[] b = new byte[1]; private int bufferLength; private boolean closed; private SSHException error; public ChannelOutputStream(Channel chan, Transport trans, Window.Remote win) { this.chan = chan; this.trans = trans; this.win = win; prepBuffer(); } private void prepBuffer() { bufferLength = 0; buffer.rpos(5); buffer.wpos(5); buffer.putMessageID(Message.CHANNEL_DATA); buffer.putInt(0); // meant to be recipient buffer.putInt(0); // meant to be data length } @Override public synchronized void write(int w) throws IOException { b[0] = (byte) w; write(b, 0, 1); } @Override public synchronized void write(byte[] data, int off, int len) throws IOException { checkClose(); while (len > 0) { final int x = Math.min(len, win.getMaxPacketSize() - bufferLength); if (x <= 0) { flush(); continue; } buffer.putRawBytes(data, off, x); bufferLength += x; off += x; len -= x; } } @Override public synchronized void notifyError(SSHException error) { this.error = error; } private synchronized void checkClose() throws SSHException { if (closed) if (error != null) throw error; else throw new ConnectionException("Stream closed"); } @Override public synchronized void close() throws IOException { if (!closed) try { flush(); chan.sendEOF(); } finally { setClosed(); } } public synchronized void setClosed() { closed = true; } @Override public synchronized void flush() throws IOException { checkClose(); if (bufferLength <= 0) // No data to send return; putRecipientAndLength(); try { win.waitAndConsume(bufferLength); trans.write(buffer); } finally { prepBuffer(); } } private void putRecipientAndLength() { final int origPos = buffer.wpos(); buffer.wpos(6); buffer.putInt(chan.getRecipient()); buffer.putInt(bufferLength); buffer.wpos(origPos); } @Override public String toString() { return "< ChannelOutputStream for Channel #" + chan.getID() + " >"; } }