Utils: added simple Protobuf creator

This commit is contained in:
Daniel Dakhno 2022-09-15 01:40:05 +02:00
parent 8c69a8a007
commit 858c53efb8
10 changed files with 343 additions and 0 deletions

View File

@ -0,0 +1,24 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf;
import java.io.ByteArrayOutputStream;
public class ProtobufUtils {
public static byte[] encode_varint(int value){
if(value == 0){
return new byte[]{0x00};
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream(4);
while(value > 0){
byte newByte = (byte)(value & 0b01111111);
value >>= 7;
if(value != 0){
newByte |= 0b10000000;
}
bytes.write(newByte);
}
return bytes.toByteArray();
}
}

View File

@ -0,0 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class BytesMessageField extends MessageField{
private byte[] fieldValueBytes;
protected BytesMessageField(int fieldNumber, byte[] value) {
super(fieldNumber, FieldType.LENGTH_DELIMITED);
fieldValueBytes = value;
}
@Override
public void encode(ByteArrayOutputStream os) throws IOException {
}
}

View File

@ -0,0 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
public class Int32MessageField extends VarintMessageField{
public Int32MessageField(int fieldNumber, int value){
super(fieldNumber, value);
this.fieldType = FieldType.INT_32_BIT;
}
}

View File

@ -0,0 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
public class Int64MessageField extends VarintMessageField{
public Int64MessageField(int fieldNumber, int value){
super(fieldNumber, value);
this.fieldType = FieldType.INT_64_BIT;
}
}

View File

@ -0,0 +1,58 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
public abstract class MessageField {
public static enum FieldType{
VARINT,
INT_64_BIT,
LENGTH_DELIMITED,
START_GROUP,
END_GROUP,
INT_32_BIT
}
int fieldNumber;
FieldType fieldType;
protected MessageField(int fieldNumber, FieldType fieldType){
this.fieldNumber = fieldNumber;
this.fieldType = fieldType;
}
protected byte[] getStartBytes(){
ByteArrayOutputStream bytes = new ByteArrayOutputStream(4);
byte result = (byte) (fieldType.ordinal() & 0b111);
int fieldNumber = this.fieldNumber;
result |= (fieldNumber & 0b1111) << 3;
fieldNumber >>= 4;
if(fieldNumber > 0){
result |= 0b10000000;
}
bytes.write(result);
while(fieldNumber > 0){
byte newByte = (byte)(fieldNumber & 0b01111111);
fieldNumber >>= 7;
if(fieldNumber != 0){
newByte |= 0b10000000;
}
bytes.write(newByte);
}
return bytes.toByteArray();
}
abstract public void encode(ByteArrayOutputStream os) throws IOException;
public byte[] encodeToBytes() throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
encode(os);
return os.toByteArray();
}
}

View File

@ -0,0 +1,27 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
public class NestedMessageField extends MessageField{
MessageField[] children;
public NestedMessageField(int fieldNumber, MessageField... children) {
super(fieldNumber, FieldType.LENGTH_DELIMITED);
this.children = children;
}
@Override
public void encode(ByteArrayOutputStream os) throws IOException {
ByteArrayOutputStream childStream = new ByteArrayOutputStream();
for(MessageField child : children){
child.encode(childStream);
}
os.write(getStartBytes());
os.write(ProtobufUtils.encode_varint(childStream.size()));
os.write(childStream.toByteArray());
}
}

View File

@ -0,0 +1,23 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
public class RootMessageField extends NestedMessageField{
public RootMessageField(MessageField... children) {
super(0, children);
}
@Override
protected byte[] getStartBytes() {
return new byte[0];
}
/*
@Override
public void encode(ByteArrayOutputStream os) throws IOException {
ByteArrayOutputStream childrenOs = new ByteArrayOutputStream();
super.encode(childrenOs);
os.write(utils.encode_varint(childrenOs.size()));
os.write(childrenOs.toByteArray());
}
*/
}

View File

@ -0,0 +1,24 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
public class StringMessageField extends MessageField{
String fieldValueString;
public StringMessageField(int fieldNumber, String value) {
super(fieldNumber, FieldType.LENGTH_DELIMITED);
fieldValueString = value;
}
@Override
public void encode(ByteArrayOutputStream os) throws IOException {
os.write(getStartBytes());
os.write(ProtobufUtils.encode_varint(fieldValueString.length()));
os.write(fieldValueString.getBytes());
}
}

View File

@ -0,0 +1,24 @@
package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
public class VarintMessageField extends MessageField{
int fieldValueInt;
public VarintMessageField(int fieldNumber, int value){
super(fieldNumber, FieldType.VARINT);
this.fieldValueInt = value;
}
@Override
public void encode(ByteArrayOutputStream os) throws IOException {
os.write(getStartBytes());
os.write(ProtobufUtils.encode_varint(fieldValueInt));
}
}

View File

@ -0,0 +1,129 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import junit.framework.TestCase;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.NestedMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.RootMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.StringMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.VarintMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
public class ProtobufTest extends TestCase {
private void assertEncodeVarint(byte[] expected, int valueToEncode){
byte[] encoded = ProtobufUtils.encode_varint(valueToEncode);
assertEquals(expected.length, encoded.length);
for(int i = 0; i < expected.length; i++){
assertEquals(expected[i], encoded[i]);
}
}
public void testEncodeVarint(){
assertEncodeVarint(new byte[]{0x00}, 0);
assertEncodeVarint(new byte[]{0x01}, 1);
assertEncodeVarint(new byte[]{0b1111111}, 0b1111111);
assertEncodeVarint(new byte[]{(byte)0b11111111, 0b00000001}, 0b11111111);
assertEncodeVarint(new byte[]{(byte)0xeb, (byte)0x86, (byte)0x4e}, 1278827);
assertEncodeVarint(new byte[]{(byte)0xf9, (byte) 0xa5 , (byte) 0x3c}, 987897);
assertEncodeVarint(new byte[]{(byte) 0xfd, (byte) 0xe5, (byte) 0x90, (byte) 0x01}, 2372349);
}
private void assertEncodeVarintMessageField(byte[] expected, int fieldId, int value) throws IOException {
VarintMessageField field = new VarintMessageField(fieldId, value);
ByteArrayOutputStream os = new ByteArrayOutputStream();
field.encode(os);
byte[] result = os.toByteArray();
assertEquals(expected.length, result.length);
for(int i = 0; i < expected.length; i++){
assertEquals(expected[i], result[i]);
}
}
public void testEncodeVarintMessageField() throws IOException {
assertEncodeVarintMessageField(new byte[]{0b00001000, 0b00000000}, 1, 0);
assertEncodeVarintMessageField(new byte[]{(byte) 0b10000000, 0b00000001, 0b01111111}, 16, 127);
assertEncodeVarintMessageField(new byte[]{(byte) 0b10000000, 0b00000001, (byte) 0xfd, (byte) 0xe5, (byte) 0x90, (byte) 0x01}, 16, 2372349);
}
private void assertEncodeStringMessageField(byte[] expected, int fieldId, String value) throws IOException {
StringMessageField field = new StringMessageField(fieldId, value);
ByteArrayOutputStream os = new ByteArrayOutputStream();
field.encode(os);
byte[] result = os.toByteArray();
assertEquals(expected.length, result.length);
for(int i = 0; i < expected.length; i++){
assertEquals(expected[i], result[i]);
}
}
private void assertEncodeRootMessageField(byte[] expected, RootMessageField value) throws IOException {
byte[] result = value.encodeToBytes();
System.out.println(StringUtils.bytesToHex(result));
assertEquals(expected.length, result.length);
for(int i = 0; i < expected.length; i++){
assertEquals(expected[i], result[i]);
}
}
public void testEncodeStringMessageField() throws IOException {
assertEncodeStringMessageField(new byte[]{0b00001010, 0b00000000}, 1, "");
assertEncodeStringMessageField(new byte[]{0b01010010, 0x04, 0x74, 0x65, 0x73, 0x74}, 10, "test");
}
public void testNestedMessageField() throws IOException {
RootMessageField startAppRequest = new RootMessageField(
new VarintMessageField(1, 2),
new NestedMessageField(16,
new StringMessageField(1, "Infrared"),
new StringMessageField(2, "RPC")
)
);
assertEncodeRootMessageField(
new byte[]{(byte) 0x14, (byte) 0x08, (byte) 0x02, (byte) 0x82, (byte) 0x01, (byte) 0x0f, (byte) 0x0a, (byte) 0x08, (byte) 0x49, (byte) 0x6e, (byte) 0x66, (byte) 0x72, (byte) 0x61, (byte) 0x72, (byte) 0x65, (byte) 0x64, (byte) 0x12, (byte) 0x03, (byte) 0x52, (byte) 0x50, (byte) 0x43},
startAppRequest
);
RootMessageField loadFileRequest = new RootMessageField(
new VarintMessageField(1, 3),
new NestedMessageField(48,
new StringMessageField(1, "/any/infrared/Remote.ir")
)
);
assertEncodeRootMessageField(
new byte[]{(byte) 0x1e, (byte) 0x08, (byte) 0x03, (byte) 0x82, (byte) 0x03, (byte) 0x19, (byte) 0x0a, (byte) 0x17, (byte) 0x2f, (byte) 0x61, (byte) 0x6e, (byte) 0x79, (byte) 0x2f, (byte) 0x69, (byte) 0x6e, (byte) 0x66, (byte) 0x72, (byte) 0x61, (byte) 0x72, (byte) 0x65, (byte) 0x64, (byte) 0x2f, (byte) 0x52, (byte) 0x65, (byte) 0x6d, (byte) 0x6f, (byte) 0x74, (byte) 0x65, (byte) 0x2e, (byte) 0x69, (byte) 0x72},
loadFileRequest
);
RootMessageField pressButtonRequest = new RootMessageField(
new VarintMessageField(1, 4),
new NestedMessageField(49,
new StringMessageField(1, "Pwr")
)
);
assertEncodeRootMessageField(new byte[]{(byte) 0x0a, (byte) 0x08, (byte) 0x04, (byte) 0x8a, (byte) 0x03, (byte) 0x05, (byte) 0x0a, (byte) 0x03, (byte) 0x50, (byte) 0x77, (byte) 0x72}, pressButtonRequest);
RootMessageField releaseButtonRequest = new RootMessageField(
new VarintMessageField(1, 5),
new NestedMessageField(50)
);
assertEncodeRootMessageField(new byte[]{(byte) 0x05, (byte) 0x08, (byte) 0x05, (byte) 0x92, (byte) 0x03, (byte) 0x00}, releaseButtonRequest);
RootMessageField exitAppRequest = new RootMessageField(
new VarintMessageField(1, 6),
new NestedMessageField(47)
);
assertEncodeRootMessageField(new byte[]{(byte) 0x05, (byte) 0x08, (byte) 0x06, (byte) 0xfa, (byte) 0x02, (byte) 0x00}, exitAppRequest);
}
}