# Byte Stream dekodieren



## continue (5. Okt 2014)

Hallo,

In meiner Applikation empfange ich einen Byte Stream via InpuStream.
Die Bytes sind dabei speziell kodiert:


```
A L D1 D2 ...DN CS
```

A... Anfang eines data frames (immer 0x20)
L... Länge, gibt an wieviel bytes in diesem data frame folgen
D1-DN... Datenbytes
CS... Ein Byte checksumme

Der Datenstream sieht als z.b. so aus:

```
0x20 0x05 0x01 0x02 0xD1 0xA1 0xAF
0x20 0x05 0x12 0x03 0x02 0x3A 0xF2
...
...
```
Nun habe ich mich gefragt wie ich das am besten (effizient) dekodieren kann.
Ich habe mir gedacht einen BufferedInputStream zu verwenden ist sicher nich verkehrt. Ich stelle mir das dann so vor (Semipseudocode):






```
BufferedInputStream bufIS = new BufferedInputStream(mInStream);
while (true ) {
    byte startFrame = bufIS.read();
    if(startFrame == 0x20) {
        int length = bufIS.read();
    
        byte dataframe[] = new bye[length];
        for (int i = 0; i<length i++) {
            dateframe[i] = bufIS.read();
        }
    
    if(verifyChecksum(bufIS.read()) postEvent(dataframe);
    }
}
```

Wäre das so okay (üblich)?
Zuerst wollte ich die Methode _public int read(byte[] b,int off,int len)_ und ein Bufferarray verwenden aber da hätte ich das problem dass read nie zuverlässig "len" Anzahl an Bytes zurückliefert.
Ist meine oben beschriebene Vorgehensweise halbwegs effizient oder geht es schneller/performanter oder einfacher?

Grüße


----------



## Tobse (6. Okt 2014)

Das ist schonmal ganz gut. Bei der geringen Menge an Bytes würde ich aber auf den BufferedStream verzichten.
Auch fäält mir auf: In deinem Beispiel stehen zwar 5 Bytes als Länge drin; abzüglich der Checksumme sinds aber nur 4.

Dein Code wird so wahrscheinlich funktionieren. Du prüfst leider nicht wieviele Bytes tatsächlich gelesen wurden daher musst du mit einer IOException/AIOOB-Exception rechnen.

Als Beispiel: Ich hätte das so gelößt:

```
InputStream in = /** get input stream */;

int ci = in.read();
byte[] buffer = new byte[255]; // buffer, initialisiert mit maximaler größe
while (ci == 0x20)
{
	ci = in.read();
	if (ci == -1)
	{
		throw new IOException("Unexpected EOF");
	}
	short len = (short) (0xFF & ci); // längen-byte als unsigned behandeln um bis zu 255 bytes zu erlauben
	
	int actuallyRead = in.read(buffer, 0, len);
	
	if (actuallyRead < len)
	{
		throw new IOException("Unexpected EOF in Data-Frame ???: wanted to read "
			+ len + " bytes, only " + actuallyRead + " found.");
	}
	
	byte checksum = (byte) (0xFF & in.read());
	if (!checkChecksum(buffer, 0, len, checksum))
	{
		throw new IOExcetpion("Invalid checksum in Data-Frame ???");
	}
	
	// trigger event
	
	ci = in.read();
}
abstract boolean checkChecksum(byte[] byteBuffer, int offset, int len, byte checksum);
```
Da hier der Buffer nur einmal initialisiert wird, sparst du dir den RAM und die Zeit dafür. Ausserdem ließt mein Code über rad(byte[], int, int) und nicht in einer Schleife via read() was effizienter sein kann, je nachdem Welcher Stream da zugrunde liegt.
Das letzte, winzige Detail ist, dassd durch die Verwendung von InputStream (im gegensatz zu BufferedInputStream) verlässliche Werte von read()/read(byte[], int, int) geliefert werden und man so detailiertere Fehlerbeschreibungen in die EOF-Exceptions bekommt.


----------



## continue (6. Okt 2014)

Hallo & Danke für die Antwort.


> Das letzte, winzige Detail ist, dassd durch die Verwendung von InputStream (im gegensatz zu BufferedInputStream) verlässliche Werte von read()/read(byte[], int, int) geliefert werden und man so detailiertere Fehlerbeschreibungen in die EOF-Exceptions bekommt.



Bist du dir da sicher? Aus der Doku von InputStream entnehme ich für read(byte[],int,int):



> Reads up to len bytes of data from the input stream into an array of bytes. An attempt is made to read as many as len bytes, but a smaller number may be read. The number of bytes actually read is returned as an integer.



d.h. is können auch weniger bytes gelesen werden was bei deinem Codebeispiel dazu führen würde dass eine IOException geworfen werden würde oder?


----------



## Tobse (6. Okt 2014)

continue hat gesagt.:


> d.h. is können auch weniger bytes gelesen werden was bei deinem Codebeispiel dazu führen würde dass eine IOException geworfen werden würde oder?



Richtig, genau darum geht es ja.

```
int actuallyRead = in.read(buffer, 0, len);
 
	if (actuallyRead < len)
	{
		throw new IOException("Unexpected EOF in Data-Frame ???: wanted to read "
			+ len + " bytes, only " + actuallyRead + " found.");
	}
```

Können vom Stream nicht so viele Bytes gelesen werden, wie in der längenangabe steht, dann wirt dein Code eine ArrayIndexOutOfBoundException ohne Fehlermeldung. Meiner wirft eine IOException mit der Fehlermeldung, dass nicht so viele Bytes gelesen werden kontnen wie nötig.
Und weil das die Bessere Fehlerbeschreibung ist, macht es das Debuggen/Testen einfacher.


----------



## Thallius (6. Okt 2014)

Und wenn man es richtig machen will, dann schaut man vorher wie groß die Datei eigentlich ist und wenn einem die Länge nicht passt, dann bringt man einen Fehler. Ansonsten liest man genau soviele Bytes ein wie die Datei lang ist.

Ist das jetzt soviel schwerer als mit Exceptions um sich zu werfen?

Gruß

Claus


----------



## Tobse (6. Okt 2014)

Thallius hat gesagt.:


> Und wenn man es richtig machen will, dann schaut man vorher wie groß die Datei eigentlich ist und wenn einem die Länge nicht passt, dann bringt man einen Fehler. Ansonsten liest man genau soviele Bytes ein wie die Datei lang ist.
> 
> Ist das jetzt soviel schwerer als mit Exceptions um sich zu werfen?



Bitte was? Du schreibst doch sonst so gute Antworten. Natürlich kann ich die Länge der Datei vorher prüfen - aber ob der Inhalt semantisch richtig ist weiss ich doch vorher nicht? Und was, wenn die Festplatte nen schluckauf kriegt? Dann reisst auch der Stream ab.
Mal gnaz davon abgesehen - wenn ich die Methode versatil einsetzen möchte, dann sollte sie von jeder Art InputStream lesen können. Und bei einem Socket hast du exakt das gleiche Problem wenn z.B. das WLAN abreisst.
Zu guter letzt: Bei einer Datei, die 20-30MB groß ist wäre es ja noch zu verantworten, dass man sie Roh in den Ram lädt. Aber was soll denn bitte ein VLC-Player bei einem 10GB großen HD-Film machen?


----------



## Thallius (6. Okt 2014)

Tobse hat gesagt.:


> Bitte was? Du schreibst doch sonst so gute Antworten. Natürlich kann ich die Länge der Datei vorher prüfen - aber ob der Inhalt semantisch richtig ist weiss ich doch vorher nicht? Und was, wenn die Festplatte nen schluckauf kriegt? Dann reisst auch der Stream ab.
> Mal gnaz davon abgesehen - wenn ich die Methode versatil einsetzen möchte, dann sollte sie von jeder Art InputStream lesen können. Und bei einem Socket hast du exakt das gleiche Problem wenn z.B. das WLAN abreisst.
> Zu guter letzt: Bei einer Datei, die 20-30MB groß ist wäre es ja noch zu verantworten, dass man sie Roh in den Ram lädt. Aber was soll denn bitte ein VLC-Player bei einem 10GB großen HD-Film machen?



Wo schreibe ich das er sie auf einmal komplett ins RAM lesen soll? Ich sage nur er soll vorher die Länge ermitteln und dann solange lesen bis er die Länge hat. Nur dann kann er sicher sein, dass das Lesen erfolgreich war. Wenn dabei Fehler auftreten, dann sind das wirklich Ausnahmen die man auch mit einer Exception abfagen sollte. 

Aber einfach eine Datei solange zu lesen bis man eine IOException bekommt und dann davon auszugehen, dass das dann EOF war, ist ja wohl totaler Humbug. Denn dann denkst du auch du hast die Datei korrekt gelesen wenn Du deinen WLAN Discinnect hast oder eine Festplatte mit Schluckauf.

Gruß

Claus


----------



## Tobse (6. Okt 2014)

Sorry, ich hatte dashier


> Ansonsten liest man genau soviele Bytes ein wie die Datei lang ist.


als "komplett in den RAM lesen" verstanden.

Ja, du hast recht, meine Methode wirft eine IOException am EOF, das ist nicht optimal. Das Problem ist doch aber, dass die Menge an Bytes in der Datei selbst steht. Und durch die bloße Größe der Datei lässt sich die Datenintegrität nicht prüfen.
Daher also ein Verbesserungsvorschlag:

```
InputStream in = /** get input stream */;

int ci = in.read();
byte[] buffer = new byte[255]; // buffer, initialisiert mit maximaler größe
while (ci != -1)
{
	if (ci != 0x20)
	{
		throw new IOException("Expected Frame-Start (0x20) but found " + ci);
	}
	
	ci = in.read();
	if (ci == -1)
	{
		throw new IOException("Unexpected EOF");
	}
	
	short len = (short) (0xFF & ci); // längen-byte als unsigned behandeln um bis zu 255 bytes zu erlauben
	
	int actuallyRead = in.read(buffer, 0, len);
	
	if (actuallyRead < len)
	{
		throw new IOException("Unexpected EOF in Data-Frame ???: wanted to read "
			+ len + " bytes, only " + actuallyRead + " found.");
	}
	
	byte checksum = (byte) (0xFF & in.read());
	if (!checkChecksum(buffer, 0, len, checksum))
	{
		throw new IOExcetpion("Invalid checksum in Data-Frame ???");
	}
	
	// trigger event

	ci = in.read();
}
```


----------

