Files

183 lines
4.7 KiB
C#

/* Copyright (c) 2016 Tom Zander <tomz@freedommail.ch>
*
* 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
*
* http://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.
*/
using System;
using System.Diagnostics;
using System.Text;
namespace CMF
{
/**
* MessageParser takes a CMF stream and parses it into tokens.
*
* A user can iterate over tokens by repeatedly calling next() and as
* long as FoundTag is returned you can then find the token-tag
* as well as the actual data. The MessageParser will actually have typed
* data based on what was encoded in the CMF stream. You can access
* this using the different getters like GetLong() GetString(). Please note
* that if the requested data is not what was present in the stream you will
* end up throwing an exception on those getters.
*/
public class MessageParser
{
public MessageParser(byte[] data, int position, int length) {
this.data = data;
this.position = position;
endPosition = position + length;
Debug.Assert(data.Length >= endPosition);
}
public enum State {
FoundTag,
EndOfDocument,
Error
};
public State Next() {
if (endPosition <= position)
return State.EndOfDocument;
int b = data[position];
ValueType type = (ValueType)(b & 7);
tag = b >> 3;
if (tag == 31) { // the tag is stored in the next byte(s)
try {
VarInt vi = new VarInt(data, ++position);
long newTag = vi.Unserialize();
if (newTag > 0xFFFF) {
Console.WriteLine("Malformed tag-type "+ newTag + " is a too large enum value");
return State.Error;
}
position = vi.GetPosition() - 1;
tag = (int) newTag;
} catch (Exception e) {
Console.WriteLine("Malformed varint; "+ e.Message);
return State.Error;
}
}
switch (type) {
case ValueType.NegativeNumber:
case ValueType.PositiveNumber:
try {
VarInt vi = new VarInt(data, ++position);
long value = vi.Unserialize();
position = vi.GetPosition();
if (type == ValueType.NegativeNumber)
this.value = value * -1;
else
this.value = value;
} catch (Exception e) {
Console.WriteLine("Malformed varint; "+ e.Message);
return State.Error;
}
break;
case ValueType.ByteArray:
case ValueType.String:
try {
VarInt vi = new VarInt(data, ++position);
int length = (int) vi.Unserialize();
if (vi.GetPosition() + length > data.Length) // need more bytes
return State.EndOfDocument;
valueState = type == ValueType.ByteArray ? Lazy.ByteArray : Lazy.String;
dataStart = vi.GetPosition();
dataLength = length;
position = dataStart + length;
} catch (Exception e) {
Console.WriteLine("Malformed varint; "+ e.Message);
return State.Error;
}
break;
case ValueType.BoolTrue:
value = true;
++position;
break;
case ValueType.BoolFalse:
value = false;
++position;
break;
default:
return State.Error;
}
return State.FoundTag;
}
public int GetTag() {
return tag;
}
public int Consumed() {
return position;
}
public void Consume(int bytes) {
Debug.Assert(bytes >= 0);
position =+ bytes;
}
public int GetInt() {
Int64 answer = (Int64) value;
if (answer > 0x7FFFFFFF)
throw new Exception("Too big");
return (int) answer;
}
public ulong GetLong() {
return (UInt64) value;
}
public bool GetBoolean() {
return (Boolean) value;
}
public String GetString() {
if (valueState == Lazy.String) {
byte[] raw = new byte[dataLength];
Buffer.BlockCopy(data, dataStart, raw, 0, dataLength);
String answer = Encoding.UTF8.GetString(raw);
value = answer;
valueState = Lazy.Parsed;
return answer;
}
return (String) value;
}
public byte[] getByteArray() {
if (valueState == Lazy.ByteArray) {
byte[] answer = new byte[dataLength];
Buffer.BlockCopy(data, dataStart, answer, 0, dataLength);
value = answer;
return answer;
}
return new byte[0];
}
private byte[] data = null;
private int position = 0;
private int endPosition = 0;
private int tag = 0;
private enum Lazy {
Parsed,
ByteArray,
String
};
private Lazy valueState = Lazy.Parsed;
private int dataStart = -1;
private int dataLength = -1;
private Object value = null;
}
}