blob: 43b742b02deb0a08cf3442acf5283e76aa142185 [file] [log] [blame]
/*
* Copyright (C) 2008, 2010 Google Inc.
* Copyright (C) 2008, 2009 Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.transport;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.OutputStream;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.RawParseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Write Git style pkt-line formatting to an output stream.
* <p>
* This class is not thread safe and may issue multiple writes to the underlying
* stream for each method call made.
* <p>
* This class performs no buffering on its own. This makes it suitable to
* interleave writes performed by this class with writes performed directly
* against the underlying OutputStream.
*/
public class PacketLineOut {
private static final Logger log = LoggerFactory.getLogger(PacketLineOut.class);
private final OutputStream out;
private final byte[] lenbuffer;
private final boolean logEnabled;
private boolean flushOnEnd;
private boolean usingSideband;
/**
* Create a new packet line writer.
*
* @param outputStream
* stream.
*/
public PacketLineOut(OutputStream outputStream) {
this(outputStream, true);
}
/**
* Create a new packet line writer that potentially doesn't log.
*
* @param outputStream
* stream.
* @param enableLogging
* {@code false} to suppress all logging; {@code true} to log
* normally
* @since 5.11
*/
public PacketLineOut(OutputStream outputStream, boolean enableLogging) {
out = outputStream;
lenbuffer = new byte[5];
flushOnEnd = true;
logEnabled = enableLogging;
}
/**
* Set the flush behavior during {@link #end()}.
*
* @param flushOnEnd
* if true, a flush-pkt written during {@link #end()} also
* flushes the underlying stream.
*/
public void setFlushOnEnd(boolean flushOnEnd) {
this.flushOnEnd = flushOnEnd;
}
/**
* Whether is using sideband
*
* @return whether to add a sideband designator to each non-flush and
* non-delim packet
* @see #setUsingSideband
* @since 5.5
*/
public boolean isUsingSideband() {
return usingSideband;
}
/**
* Set whether to use sideband
*
* @param value
* If true, when writing packet lines, add, as the first byte, a
* sideband designator to each non-flush and non-delim packet.
* See pack-protocol.txt and protocol-v2.txt from the Git project
* for more information, specifically the "side-band" and
* "sideband-all" sections.
* @since 5.5
*/
public void setUsingSideband(boolean value) {
this.usingSideband = value;
}
/**
* Write a UTF-8 encoded string as a single length-delimited packet.
*
* @param s
* string to write.
* @throws java.io.IOException
* the packet could not be written, the stream is corrupted as
* the packet may have been only partially written.
*/
public void writeString(String s) throws IOException {
writePacket(Constants.encode(s));
}
/**
* Write a binary packet to the stream.
*
* @param packet
* the packet to write; the length of the packet is equal to the
* size of the byte array.
* @throws java.io.IOException
* the packet could not be written, the stream is corrupted as
* the packet may have been only partially written.
*/
public void writePacket(byte[] packet) throws IOException {
writePacket(packet, 0, packet.length);
}
/**
* Write a binary packet to the stream.
*
* @param buf
* the packet to write
* @param pos
* first index within {@code buf}.
* @param len
* number of bytes to write.
* @throws java.io.IOException
* the packet could not be written, the stream is corrupted as
* the packet may have been only partially written.
* @since 4.5
*/
public void writePacket(byte[] buf, int pos, int len) throws IOException {
if (usingSideband) {
formatLength(len + 5);
out.write(lenbuffer, 0, 4);
out.write(1);
} else {
formatLength(len + 4);
out.write(lenbuffer, 0, 4);
}
out.write(buf, pos, len);
if (logEnabled && log.isDebugEnabled()) {
// Escape a trailing \n to avoid empty lines in the log.
if (len > 0 && buf[pos + len - 1] == '\n') {
log.debug(
"git> " + RawParseUtils.decode(UTF_8, buf, pos, len - 1) //$NON-NLS-1$
+ "\\n"); //$NON-NLS-1$
} else {
log.debug("git> " + RawParseUtils.decode(UTF_8, buf, pos, len)); //$NON-NLS-1$
}
}
}
/**
* Write a packet delim marker (0001).
*
* @throws java.io.IOException
* the marker could not be written, the stream is corrupted
* as the marker may have been only partially written.
* @since 5.0
*/
public void writeDelim() throws IOException {
formatLength(1);
out.write(lenbuffer, 0, 4);
if (logEnabled && log.isDebugEnabled()) {
log.debug("git> 0001"); //$NON-NLS-1$
}
}
/**
* Write a packet end marker, sometimes referred to as a flush command.
* <p>
* Technically this is a magical packet type which can be detected
* separately from an empty string or an empty packet.
* <p>
* Implicitly performs a flush on the underlying OutputStream to ensure the
* peer will receive all data written thus far.
*
* @throws java.io.IOException
* the end marker could not be written, the stream is corrupted
* as the end marker may have been only partially written.
*/
public void end() throws IOException {
formatLength(0);
out.write(lenbuffer, 0, 4);
if (logEnabled && log.isDebugEnabled()) {
log.debug("git> 0000"); //$NON-NLS-1$
}
if (flushOnEnd) {
flush();
}
}
/**
* Flush the underlying OutputStream.
* <p>
* Performs a flush on the underlying OutputStream to ensure the peer will
* receive all data written thus far.
*
* @throws java.io.IOException
* the underlying stream failed to flush.
*/
public void flush() throws IOException {
out.flush();
}
private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private void formatLength(int w) {
formatLength(lenbuffer, w);
}
static void formatLength(byte[] lenbuffer, int w) {
int o = 3;
while (o >= 0 && w != 0) {
lenbuffer[o--] = hexchar[w & 0xf];
w >>>= 4;
}
while (o >= 0)
lenbuffer[o--] = '0';
}
}