xref: /aosp_15_r20/external/pigweed/pw_hdlc/java/main/dev/pigweed/pw_hdlc/Encoder.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_hdlc;
16 
17 import dev.pigweed.pw_hdlc.CustomVarInt;
18 import dev.pigweed.pw_hdlc.Protocol;
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.nio.ByteBuffer;
22 import java.util.zip.CRC32;
23 
24 /** Encodes a payload with pigweed's HDLC format */
25 public class Encoder {
26   private final CRC32 crc32 = new CRC32();
27   private final OutputStream output;
28 
29   /** Writes the payload as an HDLC unnumbered information (UI) frame. */
writeUiFrame(long address, ByteBuffer payload, OutputStream output)30   public static void writeUiFrame(long address, ByteBuffer payload, OutputStream output)
31       throws IOException {
32     Encoder encoder = new Encoder(output);
33     encoder.startUnnumberedFrame(address);
34     encoder.writeData(payload);
35     encoder.finishFrame();
36   }
37 
writeFrame(Frame frame, OutputStream output)38   public static void writeFrame(Frame frame, OutputStream output) throws IOException {
39     Encoder encoder = new Encoder(output);
40     encoder.startFrame(frame.getAddress(), frame.getControl());
41     encoder.writeData(frame.getPayload());
42     encoder.finishFrame();
43   }
44 
45   /** Returns the encoded size of a UI frame with the provided payload. */
getUiFrameEncodedSize(long address, byte[] payload)46   public static int getUiFrameEncodedSize(long address, byte[] payload) {
47     return getUiFrameEncodedSize(address, ByteBuffer.wrap(payload));
48   }
49 
getUiFrameEncodedSize(long address, ByteBuffer payload)50   public static int getUiFrameEncodedSize(long address, ByteBuffer payload) {
51     ByteCountingOutputStream output = new ByteCountingOutputStream();
52     try {
53       writeUiFrame(address, payload, output);
54     } catch (IOException e) {
55       throw new AssertionError("ByteCountingOutputStream does not throw exceptions!", e);
56     }
57     return output.writtenBytes;
58   }
59 
60   /** Returns the number of payload bytes that can be encoded in a UI frame of the given size. */
getMaxPayloadBytesInUiFrameOfSize( long address, byte[] payload, int maxEncodedUiFrameSizeBytes)61   public static int getMaxPayloadBytesInUiFrameOfSize(
62       long address, byte[] payload, int maxEncodedUiFrameSizeBytes) {
63     return getMaxPayloadBytesInUiFrameOfSize(
64         address, ByteBuffer.wrap(payload), maxEncodedUiFrameSizeBytes);
65   }
66 
getMaxPayloadBytesInUiFrameOfSize( long address, ByteBuffer payload, int maxEncodedUiFrameSizeBytes)67   public static int getMaxPayloadBytesInUiFrameOfSize(
68       long address, ByteBuffer payload, int maxEncodedUiFrameSizeBytes) {
69     ByteCountingOutputStream output = new ByteCountingOutputStream();
70     Encoder encoder = new Encoder(output);
71     int payloadBytesProcessed = 0;
72 
73     try {
74       encoder.startUnnumberedFrame(address);
75       while (payload.hasRemaining()) {
76         encoder.write(payload.get());
77         if (output.writtenBytes + encoder.getFrameSuffixSize() > maxEncodedUiFrameSizeBytes) {
78           break;
79         }
80         payloadBytesProcessed += 1;
81       }
82     } catch (IOException e) {
83       throw new AssertionError("ByteCountingOutputStream does not throw exceptions!", e);
84     }
85     return payloadBytesProcessed;
86   }
87 
Encoder(OutputStream output)88   private Encoder(OutputStream output) {
89     this.output = output;
90   }
91 
startUnnumberedFrame(long address)92   private void startUnnumberedFrame(long address) throws IOException {
93     startFrame(address, Protocol.UNNUMBERED_INFORMATION);
94   }
95 
startFrame(long address, byte control)96   private void startFrame(long address, byte control) throws IOException {
97     crc32.reset();
98     output.write(Protocol.FLAG);
99     writeVarLong(address);
100     byte[] wrappedControl = {control};
101     writeData(ByteBuffer.wrap(wrappedControl));
102   }
103 
finishFrame()104   private void finishFrame() throws IOException {
105     writeCrc();
106     output.write(Protocol.FLAG);
107   }
108 
writeCrc()109   private void writeCrc() throws IOException {
110     long crc = crc32.getValue();
111     byte[] buffer = {(byte) crc, (byte) (crc >> 8), (byte) (crc >> 16), (byte) (crc >> 24)};
112     writeData(ByteBuffer.wrap(buffer));
113   }
114 
writeVarLong(long data)115   private void writeVarLong(long data) throws IOException {
116     ByteBuffer buffer = ByteBuffer.allocate(CustomVarInt.varLongSize(data));
117     CustomVarInt.putVarLong(data, buffer);
118     buffer.rewind();
119     writeData(buffer);
120   }
121 
writeData(ByteBuffer data)122   private void writeData(ByteBuffer data) throws IOException {
123     while (data.hasRemaining()) {
124       byte b = data.get();
125       write(b);
126       crc32.update(b);
127     }
128   }
129 
write(byte b)130   private void write(byte b) throws IOException {
131     if (b == Protocol.FLAG) {
132       output.write(Protocol.ESCAPED_FLAG);
133     } else if (b == Protocol.ESCAPE) {
134       output.write(Protocol.ESCAPED_ESCAPE);
135     } else {
136       output.write(b);
137     }
138   }
139 
getFrameSuffixSize()140   private int getFrameSuffixSize() {
141     int crc32Escapes = 0;
142     for (int value = (int) crc32.getValue(); value != 0; value >>>= 8) {
143       crc32Escapes += (value & 0xFF) == Protocol.FLAG || (value & 0xFF) == Protocol.ESCAPE ? 1 : 0;
144     }
145     return 4 /* CRC32 */ + crc32Escapes + 1 /* final flag byte */;
146   }
147 
148   private static class ByteCountingOutputStream extends OutputStream {
149     private int writtenBytes;
150 
151     @Override
write(int b)152     public void write(int b) {
153       writtenBytes += 1;
154     }
155   }
156 }