blob: 903e993c1bbcd6f072e1dca7132305c4f35d16c8 [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_hdlc;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.US_ASCII;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
public class EncoderTest {
private static final int OUTPUT_SIZE = 1024;
private static final byte UNNUMBERED_CONTROL = 0x03;
private static final byte ADDRESS = 0x7B;
private static final byte ENCODED_ADDRESS = (byte) ((ADDRESS << 1) | 1);
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(OUTPUT_SIZE);
@Test
public void emptyPayload() throws IOException {
ByteBuffer emptyPayload = ByteBuffer.allocate(0);
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.writeCrc(0x832d343f)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, emptyPayload, outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void oneBytePayload() throws IOException {
byte[] payload = {'A'};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write((byte) 'A')
.writeCrc(0x653c9e82)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void oneBytePayload_escape0x7d() throws IOException {
byte[] payload = {0x7d};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(Protocol.ESCAPE)
.write((byte) (0x7d ^ 0x20))
.writeCrc(0x4a53e205)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void oneBytePayload_escape0x7e() throws IOException {
byte[] payload = {0x7e};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(Protocol.ESCAPE)
.write((byte) (0x7e ^ 0x20))
.writeCrc(0xd35ab3bf)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void addressNeedsEscaping() throws IOException {
byte address = 0x7d >> 1;
byte[] payload = {'A'};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(Protocol.ESCAPE)
.write((byte) 0x5d)
.write(UNNUMBERED_CONTROL)
.write((byte) 'A')
.writeCrc(0x899E00D4)
.writeFlag()
.build();
Encoder.writeUiFrame(address, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void crcNeedsEscaping() throws IOException {
byte[] payload = {'a', 'a'};
// The CRC-32 of {ENCODED_ADDRESS, UNNUMBERED_CONTROL, "aa"} is 0x7ee04473, so
// the 0x7e must be escaped.
byte[] expectedCrc = {0x73, 0x44, (byte) 0xe0, 0x7d, 0x5e};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(payload)
.write(expectedCrc)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void multiplePayloads() throws IOException {
byte[] payload1 = {'A', 'B', 'C'};
byte[] payload2 = {'D', 'E', 'F'};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(payload1)
.writeCrc(0x72410ee4)
.writeFlag()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(payload2)
.writeCrc(0x4ba1ae47)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload1), outputStream);
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload2), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void payloadWithNoEscapes() throws IOException {
byte[] payload = "1995 toyota corolla".getBytes(US_ASCII);
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(payload)
.writeCrc(0x53ee911c)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void multiByteAddress() throws IOException {
long address = 0x3fff;
byte[] encodedAddress = {(byte) 0xfe, (byte) 0xff};
byte[] payload = "abc".getBytes(US_ASCII);
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(encodedAddress)
.write(UNNUMBERED_CONTROL)
.write(payload)
.writeCrc(0x8cee2978)
.writeFlag()
.build();
Encoder.writeUiFrame(address, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void payloadWithMultipleEscapes() throws IOException {
byte[] payload = {0x7E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x7E};
byte[] escapedPayload = {0x7D, 0x5E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x5D, 0x7D, 0x5E};
byte[] expectedResult = new ExpectedValueBuilder()
.writeFlag()
.write(ENCODED_ADDRESS)
.write(UNNUMBERED_CONTROL)
.write(escapedPayload)
.writeCrc(0x1563a4e6)
.writeFlag()
.build();
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void writeFrame() throws IOException {
byte[] payload = {5, (byte) 0xab, 0x42, 0x24, (byte) 0xf9, 0x54, (byte) 0xfb, 0x3d};
byte[] expectedResult =
new ExpectedValueBuilder().writeFlag().write(payload).writeFlag().build();
Frame frame = Frame.parse(ByteBuffer.wrap(payload));
Encoder.writeFrame(frame, outputStream);
assertThat(outputStream.toByteArray()).isEqualTo(expectedResult);
}
@Test
public void getUiFrameEncodedSize_empty() throws IOException {
byte[] payload = {};
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(Encoder.getUiFrameEncodedSize(ADDRESS, payload)).isEqualTo(outputStream.size());
}
@Test
public void getUiFrameEncodedSize_oneByte() throws IOException {
byte[] payload = {0x61};
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(Encoder.getUiFrameEncodedSize(ADDRESS, payload)).isEqualTo(outputStream.size());
}
@Test
public void getUiFrameEncodedSize_multipleEscapes() throws IOException {
byte[] payload = {0x7E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x7E};
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(Encoder.getUiFrameEncodedSize(ADDRESS, payload)).isEqualTo(outputStream.size());
}
@Test
public void getMaxPayloadBytesInUiFrameOfSize_emptyPayload() {
byte[] payload = {};
assertThat(Encoder.getMaxPayloadBytesInUiFrameOfSize(ADDRESS, payload, 1000)).isEqualTo(0);
}
@Test
public void getMaxPayloadBytesInUiFrameOfSize_emptyFrame() {
byte[] payload = {1, 2, 3, 4};
assertThat(Encoder.getMaxPayloadBytesInUiFrameOfSize(ADDRESS, payload, 0)).isEqualTo(0);
}
@Test
public void getMaxPayloadBytesInUiFrameOfSize_exactFit() throws IOException {
byte[] payload = {0x7E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x7E};
final int requiredReservedSize = Encoder.getUiFrameEncodedSize(ADDRESS, payload);
Encoder.writeUiFrame(ADDRESS, ByteBuffer.wrap(payload), outputStream);
assertThat(Encoder.getMaxPayloadBytesInUiFrameOfSize(ADDRESS, payload, requiredReservedSize))
.isEqualTo(payload.length);
// Not enough room for the last payload byte, which is escaped, so needs two bytes.
assertThat(
Encoder.getMaxPayloadBytesInUiFrameOfSize(ADDRESS, payload, requiredReservedSize - 1))
.isEqualTo(payload.length - 1);
assertThat(
Encoder.getMaxPayloadBytesInUiFrameOfSize(ADDRESS, payload, requiredReservedSize - 2))
.isEqualTo(payload.length - 1);
}
@Test
public void maxPayloadBytesInUiFrameOfSize_extraRoom() {
byte[] payload = {1, 2, 3, 4};
assertThat(Encoder.getMaxPayloadBytesInUiFrameOfSize(ADDRESS, payload, 1000))
.isEqualTo(payload.length);
}
private static class ExpectedValueBuilder {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(OUTPUT_SIZE);
public ExpectedValueBuilder writeFlag() throws IOException {
outputStream.write(Protocol.FLAG);
return this;
}
public ExpectedValueBuilder write(byte b) throws IOException {
outputStream.write(b);
return this;
}
public ExpectedValueBuilder write(byte[] buffer) throws IOException {
outputStream.write(buffer);
return this;
}
public ExpectedValueBuilder writeCrc(int crc) throws IOException {
outputStream.write(0xFF & crc);
outputStream.write(0xFF & (crc >> 8));
outputStream.write(0xFF & (crc >> 16));
outputStream.write(0xFF & (crc >> 24));
return this;
}
byte[] build() {
return outputStream.toByteArray();
}
}
}