xref: /aosp_15_r20/external/pigweed/pw_transfer/java/main/dev/pigweed/pw_transfer/VersionedChunk.java (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 package dev.pigweed.pw_transfer;
16 
17 import com.google.auto.value.AutoValue;
18 import com.google.protobuf.ByteString;
19 import dev.pigweed.pw_rpc.Status;
20 import java.util.Map;
21 import java.util.OptionalInt;
22 import java.util.OptionalLong;
23 
24 /**
25  * Abstraction of the Chunk proto that supports different protocol versions.
26  */
27 @AutoValue
28 abstract class VersionedChunk {
29   // Invalid session ID used for legacy chunks that do not map to a known session ID.
30   public static final int UNKNOWN_SESSION_ID = 0;
31 
version()32   public abstract ProtocolVersion version();
33 
type()34   public abstract Chunk.Type type();
35 
sessionId()36   public abstract int sessionId();
37 
resourceId()38   public abstract OptionalInt resourceId();
39 
offset()40   public abstract int offset();
41 
initialOffset()42   public abstract int initialOffset();
43 
windowEndOffset()44   public abstract int windowEndOffset();
45 
data()46   public abstract ByteString data();
47 
remainingBytes()48   public abstract OptionalLong remainingBytes();
49 
maxChunkSizeBytes()50   public abstract OptionalInt maxChunkSizeBytes();
51 
minDelayMicroseconds()52   public abstract OptionalInt minDelayMicroseconds();
53 
status()54   public abstract OptionalInt status();
55 
desiredSessionId()56   public abstract OptionalInt desiredSessionId();
57 
builder()58   public static Builder builder() {
59     return new AutoValue_VersionedChunk.Builder()
60         .setSessionId(UNKNOWN_SESSION_ID)
61         .setOffset(0)
62         .setWindowEndOffset(0)
63         .setData(ByteString.EMPTY)
64         .setInitialOffset(0);
65   }
66 
67   @AutoValue.Builder
68   public abstract static class Builder {
setVersion(ProtocolVersion version)69     public abstract Builder setVersion(ProtocolVersion version);
70 
setType(Chunk.Type type)71     public abstract Builder setType(Chunk.Type type);
72 
setSessionId(int sessionId)73     public abstract Builder setSessionId(int sessionId);
74 
setResourceId(int resourceId)75     public abstract Builder setResourceId(int resourceId);
76 
setOffset(int offset)77     public abstract Builder setOffset(int offset);
78 
setInitialOffset(int initialOffset)79     public abstract Builder setInitialOffset(int initialOffset);
80 
setWindowEndOffset(int windowEndOffset)81     public abstract Builder setWindowEndOffset(int windowEndOffset);
82 
setData(ByteString data)83     public abstract Builder setData(ByteString data);
84 
setRemainingBytes(long remainingBytes)85     public abstract Builder setRemainingBytes(long remainingBytes);
86 
setMaxChunkSizeBytes(int maxChunkSizeBytes)87     public abstract Builder setMaxChunkSizeBytes(int maxChunkSizeBytes);
88 
setMinDelayMicroseconds(int minDelayMicroseconds)89     public abstract Builder setMinDelayMicroseconds(int minDelayMicroseconds);
90 
setStatus(Status status)91     public final Builder setStatus(Status status) {
92       return setStatus(status.code());
93     }
94 
setStatus(int statusCode)95     abstract Builder setStatus(int statusCode);
96 
setDesiredSessionId(int desiredSessionId)97     abstract Builder setDesiredSessionId(int desiredSessionId);
98 
build()99     public abstract VersionedChunk build();
100   }
101 
fromMessage(Chunk chunk, Map<Integer, Integer> legacyIdToSessionId)102   public static VersionedChunk fromMessage(Chunk chunk, Map<Integer, Integer> legacyIdToSessionId) {
103     Builder builder = builder();
104 
105     ProtocolVersion version;
106     if (chunk.hasProtocolVersion()) {
107       if (chunk.getProtocolVersion() < ProtocolVersion.values().length) {
108         version = ProtocolVersion.values()[chunk.getProtocolVersion()];
109       } else {
110         version = ProtocolVersion.UNKNOWN;
111       }
112     } else if (chunk.hasSessionId()) {
113       version = ProtocolVersion.VERSION_TWO;
114     } else {
115       version = ProtocolVersion.LEGACY;
116     }
117     builder.setVersion(version);
118 
119     if (chunk.hasType()) {
120       builder.setType(chunk.getType());
121     } else if (chunk.getOffset() == 0 && chunk.getData().isEmpty() && !chunk.hasStatus()) {
122       builder.setType(Chunk.Type.START);
123     } else if (!chunk.getData().isEmpty()) {
124       builder.setType(Chunk.Type.DATA);
125     } else if (chunk.hasStatus()) {
126       builder.setType(Chunk.Type.COMPLETION);
127     } else {
128       builder.setType(Chunk.Type.PARAMETERS_RETRANSMIT);
129     }
130 
131     // For legacy chunks, use the transfer ID as both the resource and session IDs.
132     if (version == ProtocolVersion.LEGACY) {
133       builder.setSessionId(
134           legacyIdToSessionId.getOrDefault(chunk.getTransferId(), UNKNOWN_SESSION_ID));
135       builder.setResourceId(chunk.getTransferId());
136       if (chunk.hasStatus()) {
137         builder.setType(Chunk.Type.COMPLETION);
138       }
139     } else {
140       builder.setSessionId(chunk.getSessionId());
141     }
142 
143     if (chunk.hasDesiredSessionId()) {
144       builder.setDesiredSessionId(chunk.getDesiredSessionId());
145     }
146 
147     builder.setOffset((int) chunk.getOffset()).setData(chunk.getData());
148 
149     builder.setInitialOffset((int) chunk.getInitialOffset());
150 
151     if (chunk.hasResourceId()) {
152       builder.setResourceId(chunk.getResourceId());
153     }
154     if (chunk.hasPendingBytes()) {
155       builder.setWindowEndOffset((int) chunk.getOffset() + chunk.getPendingBytes());
156     } else {
157       builder.setWindowEndOffset(chunk.getWindowEndOffset());
158     }
159     if (chunk.hasRemainingBytes()) {
160       builder.setRemainingBytes(chunk.getRemainingBytes());
161     }
162     if (chunk.hasMaxChunkSizeBytes()) {
163       builder.setMaxChunkSizeBytes(chunk.getMaxChunkSizeBytes());
164     }
165     if (chunk.hasMinDelayMicroseconds()) {
166       builder.setMinDelayMicroseconds(chunk.getMinDelayMicroseconds());
167     }
168     if (chunk.hasStatus()) {
169       builder.setStatus(chunk.getStatus());
170     }
171     return builder.build();
172   }
173 
createInitialChunk( ProtocolVersion desiredVersion, int resourceId, int sessionId)174   public static VersionedChunk.Builder createInitialChunk(
175       ProtocolVersion desiredVersion, int resourceId, int sessionId) {
176     VersionedChunk.Builder builder =
177         builder().setVersion(desiredVersion).setType(Chunk.Type.START).setResourceId(resourceId);
178 
179     if (desiredVersion != ProtocolVersion.LEGACY) {
180       builder.setDesiredSessionId(sessionId);
181     }
182 
183     return builder;
184   }
185 
toMessage()186   public Chunk toMessage() {
187     Chunk.Builder chunk = Chunk.newBuilder()
188                               .setType(type())
189                               .setOffset(offset())
190                               .setWindowEndOffset(windowEndOffset())
191                               .setData(data())
192                               .setInitialOffset(initialOffset());
193 
194     remainingBytes().ifPresent(chunk::setRemainingBytes);
195     maxChunkSizeBytes().ifPresent(chunk::setMaxChunkSizeBytes);
196     minDelayMicroseconds().ifPresent(chunk::setMinDelayMicroseconds);
197     status().ifPresent(chunk::setStatus);
198     desiredSessionId().ifPresent(chunk::setDesiredSessionId);
199 
200     // The resourceId is only needed for START chunks.
201     if (type() == Chunk.Type.START) {
202       chunk.setResourceId(resourceId().getAsInt()); // resourceId must be set for start chunks
203     }
204 
205     // session_id did not exist in the legacy protocol, so don't send it.
206     if (version() != ProtocolVersion.LEGACY && sessionId() != UNKNOWN_SESSION_ID) {
207       chunk.setSessionId(sessionId());
208     }
209 
210     if (shouldEncodeLegacyFields()) {
211       chunk.setTransferId(resourceId().getAsInt()); // resourceId must be set for legacy transfers
212 
213       if (chunk.getWindowEndOffset() != 0) {
214         chunk.setPendingBytes(chunk.getWindowEndOffset() - offset());
215       }
216     }
217 
218     if (isInitialHandshakeChunk()) {
219       chunk.setProtocolVersion(version().ordinal());
220     }
221 
222     return chunk.build();
223   }
224 
isInitialHandshakeChunk()225   private boolean isInitialHandshakeChunk() {
226     return version() == ProtocolVersion.VERSION_TWO
227         && (type() == Chunk.Type.START || type() == Chunk.Type.START_ACK
228             || type() == Chunk.Type.START_ACK_CONFIRMATION);
229   }
230 
requestsTransmissionFromOffset()231   public final boolean requestsTransmissionFromOffset() {
232     return type() == Chunk.Type.PARAMETERS_RETRANSMIT || type() == Chunk.Type.START_ACK_CONFIRMATION
233         || type() == Chunk.Type.START;
234   }
235 
shouldEncodeLegacyFields()236   private boolean shouldEncodeLegacyFields() {
237     return version() == ProtocolVersion.LEGACY || type() == Chunk.Type.START;
238   }
239 }
240