blob: 9fdfb4d1ead99680fd7e7ea6d3e9d4b98fd78449 [file] [log] [blame]
// Copyright 2022 The Pigweed Authors
//
// 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
//
// https://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 dev.pigweed.pw_transfer;
import com.google.auto.value.AutoValue;
import com.google.protobuf.ByteString;
import dev.pigweed.pw_rpc.Status;
import java.util.OptionalInt;
import java.util.OptionalLong;
/**
* Abstraction of the Chunk proto that supports different protocol versions.
*/
@AutoValue
abstract class VersionedChunk {
public static final int UNASSIGNED_SESSION_ID = 0;
public abstract ProtocolVersion version();
public abstract Chunk.Type type();
public abstract int sessionId();
public abstract OptionalInt resourceId();
public abstract int offset();
public abstract int windowEndOffset();
public abstract ByteString data();
public abstract OptionalLong remainingBytes();
public abstract OptionalInt maxChunkSizeBytes();
public abstract OptionalInt minDelayMicroseconds();
public abstract OptionalInt status();
public static Builder builder() {
return new AutoValue_VersionedChunk.Builder()
.setSessionId(UNASSIGNED_SESSION_ID)
.setOffset(0)
.setWindowEndOffset(0)
.setData(ByteString.EMPTY);
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setVersion(ProtocolVersion version);
public abstract Builder setType(Chunk.Type type);
public abstract Builder setSessionId(int sessionId);
public abstract Builder setResourceId(int resourceId);
public abstract Builder setOffset(int offset);
public abstract Builder setWindowEndOffset(int windowEndOffset);
public abstract Builder setData(ByteString data);
public abstract Builder setRemainingBytes(long remainingBytes);
public abstract Builder setMaxChunkSizeBytes(int maxChunkSizeBytes);
public abstract Builder setMinDelayMicroseconds(int minDelayMicroseconds);
public final Builder setStatus(Status status) {
return setStatus(status.code());
}
abstract Builder setStatus(int statusCode);
public abstract VersionedChunk build();
}
public static VersionedChunk fromMessage(Chunk chunk) {
Builder builder = builder();
ProtocolVersion version;
if (chunk.hasProtocolVersion()) {
if (chunk.getProtocolVersion() < ProtocolVersion.values().length) {
version = ProtocolVersion.values()[chunk.getProtocolVersion()];
} else {
version = ProtocolVersion.UNKNOWN;
}
} else if (chunk.hasSessionId()) {
version = ProtocolVersion.VERSION_TWO;
} else {
version = ProtocolVersion.LEGACY;
}
builder.setVersion(version);
if (chunk.hasType()) {
builder.setType(chunk.getType());
} else if (chunk.getOffset() == 0 && chunk.getData().isEmpty() && !chunk.hasStatus()) {
builder.setType(Chunk.Type.START);
} else if (!chunk.getData().isEmpty()) {
builder.setType(Chunk.Type.DATA);
} else if (chunk.hasStatus()) {
builder.setType(Chunk.Type.COMPLETION);
} else {
builder.setType(Chunk.Type.PARAMETERS_RETRANSMIT);
}
// For legacy chunks, use the transfer ID as both the resource and session IDs.
if (version == ProtocolVersion.LEGACY) {
builder.setSessionId(chunk.getTransferId());
builder.setResourceId(chunk.getTransferId());
if (chunk.hasStatus()) {
builder.setType(Chunk.Type.COMPLETION);
}
} else {
builder.setSessionId(chunk.getSessionId());
}
builder.setOffset((int) chunk.getOffset()).setData(chunk.getData());
if (chunk.hasResourceId()) {
builder.setResourceId(chunk.getResourceId());
}
if (chunk.hasPendingBytes()) {
builder.setWindowEndOffset((int) chunk.getOffset() + chunk.getPendingBytes());
} else {
builder.setWindowEndOffset(chunk.getWindowEndOffset());
}
if (chunk.hasRemainingBytes()) {
builder.setRemainingBytes(chunk.getRemainingBytes());
}
if (chunk.hasMaxChunkSizeBytes()) {
builder.setMaxChunkSizeBytes(chunk.getMaxChunkSizeBytes());
}
if (chunk.hasMinDelayMicroseconds()) {
builder.setMinDelayMicroseconds(chunk.getMinDelayMicroseconds());
}
if (chunk.hasStatus()) {
builder.setStatus(chunk.getStatus());
}
return builder.build();
}
public static VersionedChunk.Builder createInitialChunk(
ProtocolVersion desiredVersion, int resourceId) {
return builder().setVersion(desiredVersion).setType(Chunk.Type.START).setResourceId(resourceId);
}
public Chunk toMessage() {
Chunk.Builder chunk = Chunk.newBuilder()
.setType(type())
.setOffset(offset())
.setWindowEndOffset(windowEndOffset())
.setData(data());
resourceId().ifPresent(chunk::setResourceId);
remainingBytes().ifPresent(chunk::setRemainingBytes);
maxChunkSizeBytes().ifPresent(chunk::setMaxChunkSizeBytes);
minDelayMicroseconds().ifPresent(chunk::setMinDelayMicroseconds);
status().ifPresent(chunk::setStatus);
// session_id did not exist in the legacy protocol, so don't send it.
if (version() != ProtocolVersion.LEGACY && sessionId() != UNASSIGNED_SESSION_ID) {
chunk.setSessionId(sessionId());
}
if (shouldEncodeLegacyFields()) {
chunk.setTransferId(resourceId().orElse(sessionId()));
if (chunk.getWindowEndOffset() != 0) {
chunk.setPendingBytes(chunk.getWindowEndOffset() - offset());
}
}
if (isInitialHandshakeChunk()) {
chunk.setProtocolVersion(version().ordinal());
}
return chunk.build();
}
private boolean isInitialHandshakeChunk() {
return version() == ProtocolVersion.VERSION_TWO
&& (type() == Chunk.Type.START || type() == Chunk.Type.START_ACK
|| type() == Chunk.Type.START_ACK_CONFIRMATION);
}
public final boolean requestsTransmissionFromOffset() {
return type() == Chunk.Type.PARAMETERS_RETRANSMIT || type() == Chunk.Type.START_ACK_CONFIRMATION
|| type() == Chunk.Type.START;
}
private boolean shouldEncodeLegacyFields() {
return version() == ProtocolVersion.LEGACY || type() == Chunk.Type.START;
}
}