AbstractConnector.java

/*
 * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> 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.sshd.agent;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.Objects;

import org.apache.sshd.agent.SshAgentConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.eclipse.jgit.internal.transport.sshd.SshdText;

/**
 * Provides some utility methods for implementing {@link Connector}s.
 *
 * @since 6.0
 */
public abstract class AbstractConnector implements Connector {

	// A somewhat sane lower bound for the maximum reply length
	private static final int MIN_REPLY_LENGTH = 8 * 1024;

	/**
	 * Default maximum reply length. 256kB is the OpenSSH limit.
	 */
	protected static final int DEFAULT_MAX_REPLY_LENGTH = 256 * 1024;

	private final int maxReplyLength;

	/**
	 * Creates a new instance using the {@link #DEFAULT_MAX_REPLY_LENGTH}.
	 */
	protected AbstractConnector() {
		this(DEFAULT_MAX_REPLY_LENGTH);
	}

	/**
	 * Creates a new instance.
	 *
	 * @param maxReplyLength
	 *            maximum number of payload bytes we're ready to accept
	 */
	protected AbstractConnector(int maxReplyLength) {
		if (maxReplyLength < MIN_REPLY_LENGTH) {
			throw new IllegalArgumentException(
					"Maximum payload length too small"); //$NON-NLS-1$
		}
		this.maxReplyLength = maxReplyLength;
	}

	/**
	 * Retrieves the maximum message length this {@link AbstractConnector} is
	 * configured for.
	 *
	 * @return the maximum message length
	 */
	protected int getMaximumMessageLength() {
		return this.maxReplyLength;
	}

	/**
	 * Prepares a message for sending by inserting the command and message
	 * length.
	 *
	 * @param command
	 *            SSH agent command the request is for
	 * @param message
	 *            about to be sent, including the 5 spare bytes at the front
	 * @throws IllegalArgumentException
	 *             if {@code message} has less than 5 bytes
	 */
	protected void prepareMessage(byte command, byte[] message)
			throws IllegalArgumentException {
		Objects.requireNonNull(message);
		if (message.length < 5) {
			// No translation; internal error
			throw new IllegalArgumentException("Message buffer for " //$NON-NLS-1$
					+ SshAgentConstants.getCommandMessageName(command)
					+ " must have at least 5 bytes; have only " //$NON-NLS-1$
					+ message.length);
		}
		BufferUtils.putUInt(message.length - 4, message);
		message[4] = command;
	}

	/**
	 * Checks the received length of a reply.
	 *
	 * @param command
	 *            SSH agent command the reply is for
	 * @param length
	 *            length as received: number of payload bytes
	 * @return the length as an {@code int}
	 * @throws IOException
	 *             if the length is invalid
	 */
	protected int toLength(byte command, byte[] length)
			throws IOException {
		long l = BufferUtils.getUInt(length);
		if (l <= 0 || l > maxReplyLength - 4) {
			throw new SshException(MessageFormat.format(
					SshdText.get().sshAgentReplyLengthError,
					Long.toString(l),
					SshAgentConstants.getCommandMessageName(command)));
		}
		return (int) l;
	}
}