variable length sequence, first bit indicating end of sequence, preon
How would you parse a variable length sequence of bytes where first bit (BigEndian) indicates if another byte is following using Preon?
Example
byte[] bytecode = new byte[] {
(byte) 0xf2, (byte) 0xbf, (byte) 0xbf, (byte) 0xbf, (byte) 0x50
};
Notes
- first bit that indicates the next is discarded in final payload
- version of Preon used for this post was 1.1
Result bytes (in decimal)
{ 114, 63, 63, 63, 80 }
Already tried
@BoundList + @Choices(with conditions)
Limbo exp lang doesn't support method calls, so you can't detect end of stream (previous needs to have sign 1 an开发者_如何学God current block needs to be the last, i.e. sign needs to be 0)
Recursive approach with @If
public static class Entry {
@BoundNumber(size = "1", byteOrder = ByteOrder.BigEndian)
private byte hasNext;
@BoundNumber(size = "7", byteOrder = ByteOrder.BigEndian)
private byte payload;
@If("hasNext > 0")
@BoundNumber(size = "1", byteOrder = ByteOrder.BigEndian)
private byte hasNext1;
@If("hasNext > 0")
@BoundNumber(size = "7", byteOrder = ByteOrder.BigEndian)
private byte payload1;
@If("hasNext1 > 0")
@BoundObject
private Entry nextEntry;
@Override
public String toString() {
return hasNext > 0 ? String.valueOf(payload) : String.valueOf(payload)
+ ", " + String.valueOf(payload1);
}
//...
}
For some reason, for example mentioned above, Preon will only parse 2 instances of Entry (parent and child) even when there should be 3.
Thanks.
For this, I would suggest implementing your own Codec or CodecDecorator, depending on what you want to do. If all you want to do is store the sequence of bytes in your own byte array, then creating your own Codec and hooking it up with the framework should be fairly easy.
Here is an implementation of Codec that probably comes close to what you are looking for:
VariableLengthByteArrayCodec:
package org.codehaus.preon.sample.varlength;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.codehaus.preon.Builder;
import org.codehaus.preon.Codec;
import org.codehaus.preon.CodecDescriptor;
import org.codehaus.preon.DecodingException;
import org.codehaus.preon.Resolver;
import org.codehaus.preon.buffer.BitBuffer;
import org.codehaus.preon.channel.BitChannel;
import org.codehaus.preon.el.Expression;
import nl.flotsam.pecia.Documenter;
import nl.flotsam.pecia.ParaContents;
import nl.flotsam.pecia.SimpleContents;
public class VariableLengthByteArrayCodec implements Codec<byte[]> {
public byte[] decode(BitBuffer buffer, Resolver resolver, Builder builder) throws DecodingException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
boolean cont = true;
while (cont) {
byte b = buffer.readAsByte(8);
bout.write(b);
cont = (b & (1 << 7)) > 0;
}
return bout.toByteArray();
}
public void encode(byte[] value, BitChannel channel, Resolver resolver) throws IOException {
channel.write(value, 0, value.length - 1);
}
public Expression<Integer, Resolver> getSize() {
return null;
}
public CodecDescriptor getCodecDescriptor() {
return new CodecDescriptor() {
public <C extends ParaContents<?>> Documenter<C> summary() {
return new Documenter<C>() {
public void document(C target) {
target.document(reference(Adjective.A, true));
target.text(".");
}
};
}
public <C extends ParaContents<?>> Documenter<C> reference(final Adjective adjective, final boolean startWithCapital) {
return new Documenter<C>() {
public void document(C target) {
target.text(adjective.asTextPreferA(startWithCapital))
.text("variable length encoded byte array.");
}
};
}
public <C extends SimpleContents<?>> Documenter<C> details(String bufferReference) {
return new Documenter<C>() {
public void document(C target) {
target.para()
.text("The number of bytes is determined by the ")
.text("leading bit of the individual bytes; ")
.text("if the first bit of a byte is 1, then ")
.text("more bytes are expted to follow.");
}
};
}
public boolean requiresDedicatedSection() {
return false;
}
public String getTitle() {
assert requiresDedicatedSection();
return null;
}
};
}
public Class<?>[] getTypes() {
return new Class<?>[] { Byte[].class };
}
public Class<?> getType() {
return Byte[].class;
}
}
VariableLengthByteArrayCodecFactory:
package org.codehaus.preon.sample.varlength;
import java.lang.reflect.AnnotatedElement;
import org.codehaus.preon.Codec;
import org.codehaus.preon.CodecFactory;
import org.codehaus.preon.ResolverContext;
public class VariableLengthByteArrayCodecFactory implements CodecFactory {
public <T> Codec<T> create(AnnotatedElement metadata, Class<T> type, ResolverContext context) {
if (metadata != null && metadata.isAnnotationPresent(VarLengthEncoded.class) && type == byte[].class) {
return (Codec<T>) new VariableLengthByteArrayCodec();
} else {
return null;
}
}
}
VarLengthEncoded:
package org.codehaus.preon.sample.varlength;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface VarLengthEncoded {
}
And then finally, this how you use it:
public static class SomeHolder {
@VarLengthEncoded byte[] value;
public byte[] getValue() {
return value;
}
}
...
Codec<SomeHolder> codec = Codecs.create(SomeHolder.class, new VariableLengthByteArrayCodecFactory());
SomeHolder holder = Codecs.decode(codec, (byte) 0xff, (byte) 0x0f);
assertThat(holder.getValue(), is(not(nullValue())));
assertThat(holder.getValue().length, is(2));
assertThat(holder.getValue()[0], is((byte) 0xff));
assertThat(holder.getValue()[1], is((byte) 0x0f));
That may seem as quite a bit of code, but if you check carefully, then you will notice that most code is actually making sure that there is proper description getting generated whenever you would generate documentation for classes with the @VarLengthEncoded annotation. If you don't care about documentation at all, then you can simply return a default CodecDescriptor.
So, I guess, the essence of this answer is: there are certainly cases in which providing an implementation in Preon itself would overload the framework. That doesn't mean you should rely on everything the framework has to offer by default. It just means you should plug in your own extensions.
精彩评论