PacketLineOut.java

  1. /*
  2.  * Copyright (C) 2008, 2010 Google Inc.
  3.  * Copyright (C) 2008, 2009 Robin Rosenberg <robin.rosenberg@dewire.com>
  4.  * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.transport;

  13. import static java.nio.charset.StandardCharsets.UTF_8;

  14. import java.io.IOException;
  15. import java.io.OutputStream;

  16. import org.eclipse.jgit.lib.Constants;
  17. import org.eclipse.jgit.util.RawParseUtils;
  18. import org.slf4j.Logger;
  19. import org.slf4j.LoggerFactory;

  20. /**
  21.  * Write Git style pkt-line formatting to an output stream.
  22.  * <p>
  23.  * This class is not thread safe and may issue multiple writes to the underlying
  24.  * stream for each method call made.
  25.  * <p>
  26.  * This class performs no buffering on its own. This makes it suitable to
  27.  * interleave writes performed by this class with writes performed directly
  28.  * against the underlying OutputStream.
  29.  */
  30. public class PacketLineOut {

  31.     private static final Logger log = LoggerFactory.getLogger(PacketLineOut.class);

  32.     private final OutputStream out;

  33.     private final byte[] lenbuffer;

  34.     private final boolean logEnabled;

  35.     private boolean flushOnEnd;

  36.     private boolean usingSideband;

  37.     /**
  38.      * Create a new packet line writer.
  39.      *
  40.      * @param outputStream
  41.      *            stream.
  42.      */
  43.     public PacketLineOut(OutputStream outputStream) {
  44.         this(outputStream, true);
  45.     }

  46.     /**
  47.      * Create a new packet line writer that potentially doesn't log.
  48.      *
  49.      * @param outputStream
  50.      *            stream.
  51.      * @param enableLogging
  52.      *            {@code false} to suppress all logging; {@code true} to log
  53.      *            normally
  54.      * @since 5.11
  55.      */
  56.     public PacketLineOut(OutputStream outputStream, boolean enableLogging) {
  57.         out = outputStream;
  58.         lenbuffer = new byte[5];
  59.         flushOnEnd = true;
  60.         logEnabled = enableLogging;
  61.     }

  62.     /**
  63.      * Set the flush behavior during {@link #end()}.
  64.      *
  65.      * @param flushOnEnd
  66.      *            if true, a flush-pkt written during {@link #end()} also
  67.      *            flushes the underlying stream.
  68.      */
  69.     public void setFlushOnEnd(boolean flushOnEnd) {
  70.         this.flushOnEnd = flushOnEnd;
  71.     }

  72.     /**
  73.      * @return whether to add a sideband designator to each non-flush and
  74.      *     non-delim packet
  75.      * @see #setUsingSideband
  76.      * @since 5.5
  77.      */
  78.     public boolean isUsingSideband() {
  79.         return usingSideband;
  80.     }

  81.     /**
  82.      * @param value If true, when writing packet lines, add, as the first
  83.      *     byte, a sideband designator to each non-flush and non-delim
  84.      *     packet. See pack-protocol.txt and protocol-v2.txt from the Git
  85.      *     project for more information, specifically the "side-band" and
  86.      *     "sideband-all" sections.
  87.      * @since 5.5
  88.      */
  89.     public void setUsingSideband(boolean value) {
  90.         this.usingSideband = value;
  91.     }

  92.     /**
  93.      * Write a UTF-8 encoded string as a single length-delimited packet.
  94.      *
  95.      * @param s
  96.      *            string to write.
  97.      * @throws java.io.IOException
  98.      *             the packet could not be written, the stream is corrupted as
  99.      *             the packet may have been only partially written.
  100.      */
  101.     public void writeString(String s) throws IOException {
  102.         writePacket(Constants.encode(s));
  103.     }

  104.     /**
  105.      * Write a binary packet to the stream.
  106.      *
  107.      * @param packet
  108.      *            the packet to write; the length of the packet is equal to the
  109.      *            size of the byte array.
  110.      * @throws java.io.IOException
  111.      *             the packet could not be written, the stream is corrupted as
  112.      *             the packet may have been only partially written.
  113.      */
  114.     public void writePacket(byte[] packet) throws IOException {
  115.         writePacket(packet, 0, packet.length);
  116.     }

  117.     /**
  118.      * Write a binary packet to the stream.
  119.      *
  120.      * @param buf
  121.      *            the packet to write
  122.      * @param pos
  123.      *            first index within {@code buf}.
  124.      * @param len
  125.      *            number of bytes to write.
  126.      * @throws java.io.IOException
  127.      *             the packet could not be written, the stream is corrupted as
  128.      *             the packet may have been only partially written.
  129.      * @since 4.5
  130.      */
  131.     public void writePacket(byte[] buf, int pos, int len) throws IOException {
  132.         if (usingSideband) {
  133.             formatLength(len + 5);
  134.             out.write(lenbuffer, 0, 4);
  135.             out.write(1);
  136.         } else {
  137.             formatLength(len + 4);
  138.             out.write(lenbuffer, 0, 4);
  139.         }
  140.         out.write(buf, pos, len);
  141.         if (logEnabled && log.isDebugEnabled()) {
  142.             // Escape a trailing \n to avoid empty lines in the log.
  143.             if (len > 0 && buf[pos + len - 1] == '\n') {
  144.                 log.debug(
  145.                         "git> " + RawParseUtils.decode(UTF_8, buf, pos, len - 1) //$NON-NLS-1$
  146.                                 + "\\n"); //$NON-NLS-1$
  147.             } else {
  148.                 log.debug("git> " + RawParseUtils.decode(UTF_8, buf, pos, len)); //$NON-NLS-1$
  149.             }
  150.         }
  151.     }

  152.     /**
  153.      * Write a packet delim marker (0001).
  154.      *
  155.      * @throws java.io.IOException
  156.      *             the marker could not be written, the stream is corrupted
  157.      *             as the marker may have been only partially written.
  158.      * @since 5.0
  159.      */
  160.     public void writeDelim() throws IOException {
  161.         formatLength(1);
  162.         out.write(lenbuffer, 0, 4);
  163.         if (logEnabled && log.isDebugEnabled()) {
  164.             log.debug("git> 0001"); //$NON-NLS-1$
  165.         }
  166.     }

  167.     /**
  168.      * Write a packet end marker, sometimes referred to as a flush command.
  169.      * <p>
  170.      * Technically this is a magical packet type which can be detected
  171.      * separately from an empty string or an empty packet.
  172.      * <p>
  173.      * Implicitly performs a flush on the underlying OutputStream to ensure the
  174.      * peer will receive all data written thus far.
  175.      *
  176.      * @throws java.io.IOException
  177.      *             the end marker could not be written, the stream is corrupted
  178.      *             as the end marker may have been only partially written.
  179.      */
  180.     public void end() throws IOException {
  181.         formatLength(0);
  182.         out.write(lenbuffer, 0, 4);
  183.         if (logEnabled && log.isDebugEnabled()) {
  184.             log.debug("git> 0000"); //$NON-NLS-1$
  185.         }
  186.         if (flushOnEnd) {
  187.             flush();
  188.         }
  189.     }

  190.     /**
  191.      * Flush the underlying OutputStream.
  192.      * <p>
  193.      * Performs a flush on the underlying OutputStream to ensure the peer will
  194.      * receive all data written thus far.
  195.      *
  196.      * @throws java.io.IOException
  197.      *             the underlying stream failed to flush.
  198.      */
  199.     public void flush() throws IOException {
  200.         out.flush();
  201.     }

  202.     private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
  203.             '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

  204.     private void formatLength(int w) {
  205.         formatLength(lenbuffer, w);
  206.     }

  207.     static void formatLength(byte[] lenbuffer, int w) {
  208.         int o = 3;
  209.         while (o >= 0 && w != 0) {
  210.             lenbuffer[o--] = hexchar[w & 0xf];
  211.             w >>>= 4;
  212.         }
  213.         while (o >= 0)
  214.             lenbuffer[o--] = '0';
  215.     }
  216. }