PDUs mit Python versenden

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Hallo,

ich bin grade dabei ein kleines Chat Programm zu schreiben, dass einfache PDUs über Sockets senden wird.

Eine Beispeil PDU würde so aussehen:

Code: Alles auswählen

+-------------------------------------+
|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|
+-------------------------------------+
|TIMESTAMP                            |
|                                     |
+-------------------------------------+
|TYPE   |CONTENT                     |
+--------+                            |
|                                     |
|                                     |
+-------------------------------------+
Wie würde ich das in python umsetzen? Dabei muss die Implementierung inter operabel sein, d.h. auch andere Implementierungen müssen damit zurecht kommen.

Wie kodiere ich das Content Feld ohne in Gefahr zu laufen ein Nullbyte zu senden, oder kann das gar nicht vorkommen? Es soll möglich sein UTF-8 Strings in das Content Feld zu packen.

Grüße,
anogayales
jtk
User
Beiträge: 37
Registriert: Montag 19. November 2007, 17:16

du könntest alles urlencodieren, z.B. mit den Funtionen aus der urllib

oder anders escapen, z.b. in python escaped strings
msg="Hallo\x00Wie gehts?"
escaped=repr(msg)
..
msg=escaped.decode(‘string_escape’)

das hat dann probleme mit anderen programmiersprachen und man braucht ne fehlerabfrage

das einfachste wäre ein len feld in der pdu oder die eingabe so zu gestalten, dass kein \0 eingegeben werden kann
BlackJack

@anogayales: PDU? Das Free Online Dictionary kennt 26 Bedeutungen für diese Abkürzung. Ich nehme mal an, dass Du nicht „Problematic Drug User“ oder „Pesticide Dispersal Unit“ meinst. ;-)

Was stellen die Zahlen in dem Diagramm dar? Bits? Der Typ soll wirklich in 4 Bits kodiert werden und die anderen 4 Bits gehören zum Inhalt? Du müsstest also den Inhalt umständlich aus Nibbles wieder zusammen basteln!?

Warum darf der Inhalt kein Nullbyte enthalten? Normaler UTF-8-kodierter Text sollte keine Nullbytes enthalten.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

BlackJack hat geschrieben:@anogayales: PDU? Das Free Online Dictionary kennt 26 Bedeutungen für diese Abkürzung. Ich nehme mal an, dass Du nicht „Problematic Drug User“ oder „Pesticide Dispersal Unit“ meinst. ;-)
Oh sorry, ich dachte PDU sei ein bekannter Begriff bei der Netzwerkprogrammieren. Ich meine das hier: http://en.wikipedia.org/wiki/Protocol_data_unit Informatiker Jargon eben :)
BlackJack hat geschrieben: Was stellen die Zahlen in dem Diagramm dar? Bits?
Ja genau, jeder Block ist für ein Bit reserviert. TimeStamp hätte somit 32 bit.
BlackJack hat geschrieben: Warum darf der Inhalt kein Nullbyte enthalten? Normaler UTF-8-kodierter Text sollte keine Nullbytes enthalten.
Da die Content Länge nirgends spezifiziert wurde, dachte ich, dass man solange das Content Feld entlang rattert bis man auf ein Nullbyte stößt, oder wird auch sichergestellt, dass eine PDU mit Sockets vollständig übertragen wird, wenn diese Nullbytes enthält?

Edit: Das Nullbyte Problem ist wohl nur in konstruierten Fällen ein Problem. Hast recht: UTF-8 enthält keine Nullbytes. Naja um genau zu sein enthält es genau eins. :)

In erster Linie will ich wissen, wie ich eine PDU überhaupt in Python abbilden kann. Also so, dass auch andere Programmiersprachen was damit anfangen können.

Grüße,
anogayales
BlackJack

@anogayales: Das PDU eher eine der Möglichkeiten sein sollte, die sich auf Netzwerkprogrammierung bezieht, hatte ich mir ja schon gedacht, aber auch da ist das ein „schwammiger“ Begriff. Was ist PDU denn nun genau? Für welches Protokoll steht das P in diesem konkreten Fall?

Den zweiten Teil der Bit-Frage hast Du nicht beantwortet: Sollen es wirklich 4-Bit für den Typ sein und damit alle nachfolgenden Daten so hässlich über Oktett-Grenzen hinweg verschoben werden!?

Was ist mit „Sockets übertragen“ gemeint? UDP, TCP, oder vielleicht sogar noch etwas „roheres“? Bei UDP und TCP können auch Nullbytes in den Daten enthalten sein. Die Frage ist da also eher wie Du bei Deinen Paketen feststellen willst wo die Daten zuende sind. Nullbyte als Endkennzeichen oder ein Längenfeld?

Zum zusammenbauen und auseinandernehmen von solchen Paketen bietet die Standardbibliothek `struct` oder `ctypes`. Wenn es auch etwas externes sein darf und der Paketaufbau komplexer wird, gibt es `construct`.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

BlackJack hat geschrieben:@anogayales: Das PDU eher eine der Möglichkeiten sein sollte, die sich auf Netzwerkprogrammierung bezieht, hatte ich mir ja schon gedacht, aber auch da ist das ein „schwammiger“ Begriff. Was ist PDU denn nun genau? Für welches Protokoll steht das P in diesem konkreten Fall?
Das P ist ein von uns selbst erstelles Protokoll an der Uni. Das Ganze ist noch nicht in trockenen Tüchern deswegen hier auch die schwammige Formulierung.
BlackJack hat geschrieben: Den zweiten Teil der Bit-Frage hast Du nicht beantwortet: Sollen es wirklich 4-Bit für den Typ sein und damit alle nachfolgenden Daten so hässlich über Oktett-Grenzen hinweg verschoben werden!?
Nein, das ist nur in konstruiertes Beispiel, das so nicht in unserem Protokoll vorkommt.
BlackJack hat geschrieben: Was ist mit „Sockets übertragen“ gemeint? UDP, TCP, oder vielleicht sogar noch etwas „roheres“?
Unser Protokoll verwendet sowohl UDP als auch TCP.
BlackJack hat geschrieben: Bei UDP und TCP können auch Nullbytes in den Daten enthalten sein. Die Frage ist da also eher wie Du bei Deinen Paketen feststellen willst wo die Daten zuende sind. Nullbyte als Endkennzeichen oder ein Längenfeld?
Ich hab grade nachgeschaut, in unserem Protokoll wird ein Längenfeld benutzt.

Bietet einem `construct` auch Unterstützung für Felder die durch ein Längenfeld definiert wurden oder muss ich da arbeit reinstecken :) ?

Grüße,
anogayales
BlackJack

@anogayales: Dafür bietet `construct` Unterstützung:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from construct import (
    Container, Enum, PascalString, PrefixedArray, Struct, ULInt8, ULInt32
)

Packet = Struct(
    'Packet',
    ULInt32('timestamp'),
    Enum(
        ULInt8('type'),
        normal=0,
        urgent=1,
        answer=42
    ),
    PascalString('content', ULInt32('length'), encoding='utf-8')
)


def main():
    data = Packet.build(Container(timestamp=4711, type='normal', content='abc'))
    print repr(data)
    packet = Packet.parse(data)
    print packet.timestamp
    print packet.type
    print packet.content
    packet.type = 'urgent'
    packet.content = u'Hällö Welt!'
    print packet
    print repr(Packet.build(packet))
    data = '\x17\x00\x00\x00*\x0e\x00\x00\x00zweiundvierzig'
    print repr(data)
    print Packet.parse(data)


if __name__ == '__main__':
    main()
Ausgabe:

Code: Alles auswählen

'g\x12\x00\x00\x00\x03\x00\x00\x00abc'
4711
normal
abc
Container:
    timestamp = 4711
    type = 'urgent'
    content = u'H\xe4ll\xf6 Welt!'
'g\x12\x00\x00\x01\r\x00\x00\x00H\xc3\xa4ll\xc3\xb6 Welt!'
'\x17\x00\x00\x00*\x0e\x00\x00\x00zweiundvierzig'
Container:
    timestamp = 23
    type = 'answer'
    content = u'zweiundvierzig'
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

vielen vielen Dank!

Edit:

Ich habe grade in unsere Spezifikation geschaut. Dort haben wir sowas in der Art spezifiziert:

Code: Alles auswählen

NumFields | Field1 |Field2 | ... | Field N
wobei NumFields ein 8bit int ist und FieldX ein null-terminierter-String.

Eigentlich bräuchte man meines erachtens NumFields gar nicht, aber was solls. Kann man eine solche Struktur auch mit Hilfe von Construct abbilden? Ich habe gesehen das es einen CString gibt, aber wie ich damit weiter komm seh ich jetzt auf anhieb nicht.

Kann es sein, dass ich mir hier selbst was schreiben muss?

Grüße,
anogayales
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Ich das mal kurz probiert zu realisieren. Leider bekomme ich viele Nullbytes in meinen Datenstrom, die da nicht hingehören:

Code: Alles auswählen

Fields = Struct("Fields",
                ULInt8("NumFields"),
                OptionalGreedyRepeater(CString(name="Field")))


data = Fields.build(Container(NumFields=2, Field="hello\x00yay\x00"))

print repr(data)
Ausgabe:

Code: Alles auswählen

'\x02h\x00e\x00l\x00l\x00o\x00\x00y\x00a\x00y\x00\x00'
Mach ich was Falsch, bzw. geht das auch ohne den GreedyRepeater, oder nutz ich diesen vielleicht sogar falsch?

Grüße,
anogayales
BlackJack

@anogayales: Du machst etwas falsch. So ein Repeater steht für mehr als einen Wert, denn er *wiederholt* das Konstrukt das er enthält ja. Also steht der Name `Field` für eine Sequenz von Zeichenketten. Du übergibst eine Zeichenkette — was ja eine Sequenz von Zeichenketten der Länge 1 ist. So sieht dann halt auch das Ergebnis aus. Du musst dort eine Sequenz von Zeichenketten übergeben und zwar ohne Nullbytes. Die kannst Du zwar ohne Fehlermeldung in den Zeichenketten haben, aber das lässt sich später nicht wieder parsen, weil ein Nullbyte *in* der Zeichenkette nicht mehr vom Nullbyte für das Ende unterschieden werden kann. So wäre es richtig:

Code: Alles auswählen

In [265]: Fields.build(Container(NumFields=2, Field=['hello', 'yay']))
Out[265]: '\x02hello\x00yay\x00'

In [266]: Fields.parse(_)
Out[266]: Container(Field = ['hello', 'yay'], NumFields = 2)
Das hat aber das Problem das Du Dich 1. um das Feld mit der Zahl selber kümmern musst, und 2. so ein `OptionalGreedyRepeater` nicht mehr funktioniert wenn danach noch Daten kommen, die Nullbytes enthalten. Die würde er beim parsen nämlich auch noch zu `CString` verwursten. Was Du an der Stelle haben möchtest ist viel einfacher: Ein `PrefixedArray`:

Code: Alles auswählen

In [273]: Fields = PrefixedArray(CString('Fields'))

In [274]: Fields.build(['hello', 'yay', 'cool'])
Out[274]: '\x03hello\x00yay\x00cool\x00'

In [275]: Fields.parse(_)
Out[275]: ['hello', 'yay', 'cool']

In [276]: Fields.parse(_274 + 'garbage\x00')
Out[276]: ['hello', 'yay', 'cool']
Der `OptionalGreedyRepeater` hätte im letzten Beispiel vier Zeichenketten geliefert obwohl das 'garbage' gar nicht in der Anzahl enthalten ist. Falls Du mal eine andere Grösse für den Zähler als ein Byte benötigst, kannst Du das als zweites Argument beim `PrefixedArray` angeben.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Vielen Dank!

Bisher konnte ich alle PDUs abbilden!

Grüße,
anogayales
Antworten