From 7caee026d7e303d18afb3a23bd05f44d83f176ab Mon Sep 17 00:00:00 2001 From: sc Date: Mon, 4 Dec 2000 13:20:06 +0000 Subject: First implementation of the OBEX protocol (mainly for IrDA) --- comm/ObexStream.cc | 629 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 629 insertions(+) create mode 100644 comm/ObexStream.cc (limited to 'comm/ObexStream.cc') diff --git a/comm/ObexStream.cc b/comm/ObexStream.cc new file mode 100644 index 0000000..d70caa1 --- /dev/null +++ b/comm/ObexStream.cc @@ -0,0 +1,629 @@ +/* + * Ivy League + * + * OBEX protocol + * + * Copyright 2000 + * Centre d'Etudes de la Navigation Aerienne (CENA) + * + * by Stephane Chatty + * + * $Id$ + * + */ + +#include "ObexStream.h" +#include "error.h" +#include + +#include + +IvlObexStream :: IvlObexStream (IvlAddress* a, IvlBaseScheduler* mpx) +: IvlStream (a) +{ + if (Listen () < 0) { + SysError (ErrWarn, "IvlObexStream::IvlObexStream: Listen"); + } else { + SetMode (IORead); + Add (mpx); + } +} + +IvlObexStream :: ~IvlObexStream () +{ +} + +void +IvlObexStream :: HandleRead () +{ + int fd = Accept (); + if (fd < 0) { + SysError (ErrWarn, "IvlObexStream::HandleRead: Accept"); + return; + } + + IvlObexAgent* cl = new IvlObexAgent (fd, this); +} + +#if 0 +void +IvlObexStream :: DiscoverDevices () +{ +} +#endif + +/******************************************************************************/ + +/* OBEX request codes */ +#define OBEX_FINAL_BIT 0x80 +#define OBEX_CONNECT_REQ 0x00 +#define OBEX_DISCONNECT_REQ 0x01 +#define OBEX_PUT_REQ 0x02 +#define OBEX_GET_REQ 0x03 +#define OBEX_COMMAND_REQ 0x04 +#define OBEX_SETPATH_REQ 0x05 +#define OBEX_ABORT_REQ 0x7f + +/* OBEX headers encoding */ +#define OBEX_HI_MASK 0xc0 +#define OBEX_UNICODE_HDR 0x00 +#define OBEX_STRING_HDR 0x40 +#define OBEX_BYTE_HDR 0x80 +#define OBEX_INT_HDR 0xc0 + +/* Version 1.1 */ +#define OBEX_VERSION 0x11 + +/* OBEX response codes*/ +#define OBEX_CONTINUE_RSP 0x10 +#define OBEX_SWITCH_PRO_RSP 0x11 +#define OBEX_SUCCESS_RSP 0x20 +#define OBEX_CREATED_RSP 0x21 +#define OBEX_ACCEPTED_RSP 0x22 +#define OBEX_NO_CONTENT_RSP 0x24 +#define OBEX_BAD_REQUEST_RSP 0x40 +#define OBEX_UNAUTHORIZED_RSP 0x41 +#define OBEX_PAYMENT_REQUIRED_RSP 0x42 +#define OBEX_FORBIDDEN_RSP 0x43 +#define OBEX_NOT_FOUND_RSP 0x44 +#define OBEX_METHOD_NOT_ALLOWED_RSP 0x45 +#define OBEX_CONFLICT_RSP 0x49 +#define OBEX_INTERNAL_SERVER_ERROR_RSP 0x50 +#define OBEX_NOT_IMPLEMENTED_RSP 0x51 +#define OBEX_DATABASE_FULL_RSP 0x60 +#define OBEX_DATABASE_LOCKED_RSP 0x61 + +/* OBEX header types */ +#define OBEX_HEADER_COUNT 0x00 /* Number of objects (used by connect) */ +#define OBEX_HEADER_NAME 0x01 /* Name of the object */ +#define OBEX_HEADER_TYPE 0x02 /* Type of the object */ +#define OBEX_HEADER_LENGTH 0x03 /* Total lenght of object */ +#define OBEX_HEADER_TIME 0x04 /* Last modification of object */ +#define OBEX_HEADER_DESCRIPTION 0x05 /* Description of object */ +#define OBEX_HEADER_TARGET 0x06 /* Identifies the target for the object */ +#define OBEX_HEADER_HTTP 0x07 /* an HTTP 1.x header */ +#define OBEX_HEADER_BODY 0x08 /* Data part of the object */ +#define OBEX_HEADER_BODY_END 0x09 /* Last data part of the object */ +#define OBEX_HEADER_WHO 0x0a /* Identifies the sender of the object */ +#define OBEX_HEADER_CONNECTIONID 0x0b /* Connection identifier */ +#define OBEX_HEADER_PARAMS 0x0c /* App. parameters */ +#define OBEX_HEADER_CHALLENGE 0x0d /* Auth. challenge */ +#define OBEX_HEADER_RESPONSE 0x0e /* Auth. response */ +#define OBEX_HEADER_CLASS 0x0f /* Object class */ + + +class IvlObexHeader { +public: + byte Hi; + lword Length; + lword Offset; + void* Buf; + IvlObexHeader (byte hi, lword l, lword o) : Hi (hi), Length (l), Offset (o) {} +}; + +IvlObexAgent :: IvlObexAgent (int fd, IvlObexStream* l) +: IvlBufStream (), + MyLink (l), + CurObj (0) +{ + SetMode (IORead); + IvlChannel::Open (fd); + Add (l->GetScheduler ()); + OutSize = 255; +} + +/* this is a problem: how do I make sure that agents created this way (after a +DiscoverDevices) are not also created when peers connect to me? Looks similar to +the situation with Ivy agents. */ +IvlObexAgent :: IvlObexAgent (IvlAddress* a, IvlObexStream* l) +: IvlBufStream (0, a), + MyLink (l), + CurObj (0) +{ + if (!Setup ()) { + cerr << "Obex warning. Cannot set up stream: "; + perror (""); + return; + } + SetMode (IORead); + Add (l->GetScheduler ()); + OutSize = 255; +} + +IvlObexAgent :: ~IvlObexAgent () +{ +} + + +void +IvlObexAgent :: HandleRead () +{ + if (ReadInput () <= 0) { + /* could emit a OBEX_EV_LINKERR event to the upper software layers */ + return; + } + + /* if not enough data for reading packet length, wait for future input */ + if (InBuffer.BufLength () < 3) + return; + + sword len; + InBuffer.PeekShort (len, 1); + len = ntohs (len); + + /* if packet is not complete, wait for future input */ + if (InBuffer.BufLength () < len) + return; + + + /* should add tests depending on what we are expecting */ + + /* read operation code and skip packet length */ + byte reqcode; + InBuffer.ReadByte (reqcode); + InBuffer.Flush (2); + + /* extract 'final' bit from reqcode */ + bool final = reqcode & OBEX_FINAL_BIT; + reqcode = reqcode & ~OBEX_FINAL_BIT; + + switch (reqcode) { + case OBEX_CONNECT_REQ: + ParseConnect (len, final); + break; + case OBEX_DISCONNECT_REQ: + ParseDisconnect (len, final); + break; + case OBEX_ABORT_REQ: + ParseAbort (len, final); + break; + case OBEX_PUT_REQ: + ParsePut (len, final); + break; + case OBEX_GET_REQ: + ParseGet (len, final); + break; + case OBEX_SETPATH_REQ: + ParseSetPath (len, final); + break; + case OBEX_CONTINUE_RSP: + cerr << "--> continue.\n"; + ParseHeaders (len, final); + break; + case OBEX_SWITCH_PRO_RSP: + cerr << "--> switch protocol.\n"; + ParseHeaders (len, final); + break; + case OBEX_SUCCESS_RSP: + cerr << "--> success.\n"; + ParseHeaders (len, final); + break; + case OBEX_CREATED_RSP: + cerr << "--> created.\n"; + ParseHeaders (len, final); + break; + case OBEX_ACCEPTED_RSP: + cerr << "--> accepted.\n"; + ParseHeaders (len, final); + break; + case OBEX_NO_CONTENT_RSP: + cerr << "--> no content.\n"; + ParseHeaders (len, final); + break; + case OBEX_BAD_REQUEST_RSP: + cerr << "--> bad request.\n"; + ParseHeaders (len, final); + break; + case OBEX_UNAUTHORIZED_RSP: + cerr << "--> unauthorized.\n"; + ParseHeaders (len, final); + break; + case OBEX_PAYMENT_REQUIRED_RSP: + cerr << "--> payment required.\n"; + ParseHeaders (len, final); + break; + case OBEX_FORBIDDEN_RSP: + cerr << "--> forbidden.\n"; + ParseHeaders (len, final); + break; + case OBEX_NOT_FOUND_RSP: + cerr << "--> not found.\n"; + ParseHeaders (len, final); + break; + case OBEX_METHOD_NOT_ALLOWED_RSP: + cerr << "--> not allowed.\n"; + ParseHeaders (len, final); + break; + case OBEX_CONFLICT_RSP: + cerr << "--> conflict.\n"; + ParseHeaders (len, final); + break; + case OBEX_INTERNAL_SERVER_ERROR_RSP: + cerr << "--> server error.\n"; + ParseHeaders (len, final); + break; + case OBEX_NOT_IMPLEMENTED_RSP: + cerr << "--> not implemented.\n"; + ParseHeaders (len, final); + break; + case OBEX_DATABASE_FULL_RSP: + cerr << "--> database full.\n"; + ParseHeaders (len, final); + break; + case OBEX_DATABASE_LOCKED_RSP: + cerr << "--> database locked.\n"; + ParseHeaders (len, final); + break; + default: + cerr << "unexpected request!\n"; + break; + } +} + +#if 0 + /* if this is the first packet of an object */ + if (!CurObj) { + cerr << "new object\n"; + /* allocate new object + or deliver OBEX_INTERNAL_SERVER_ERROR_RSP to peer */ + + DecodeObjectPart (); + + if (!final) + /* should deliver OBEX_EV_REQHINT to upper layers */ + ; + } else { + DecodeObjectPart (); + } + +#endif + +void +IvlObexAgent :: ParseConnect (sword len, bool final) +{ + if (len < 4) { + cerr << "IvlObexAgent: malformed connection header\n"; + /* should send OBEX_BAD_REQUEST_RSP to peer */ + /* could deliver OBEX_EV_PARSEERR to the upper software layers */ + InBuffer.Flush (); + return; + } + + /*** faudrait aussi traiter le cas ou il n'y a pas de Connect au + debut de la connexion ? */ + + byte version; + byte flags; + sword max_packet_length; + InBuffer.ReadByte (version); + InBuffer.ReadByte (flags); + InBuffer.ReadShort (max_packet_length); + OutSize = ntohs (max_packet_length); + + cerr << "--> connect: version " << (version >> 4) << "." << (version & 0x0f) + << ", " << OutSize << " bytes packets max\n"; + + IvlEvent* ev = new IvlObexAgentEvent (this, IvlObexAgentEvent::ObexNewAgent); + MyLink->NewAgents.Dispatch (*ev); + + ParseHeaders (len, final); + + + /* this will have to be improved, of course, + but the response to Connect is not like other responses */ + OutBuffer.WriteByte (OBEX_SUCCESS_RSP | OBEX_FINAL_BIT); + OutBuffer.WriteShort (htons (7)); + OutBuffer.WriteByte (OBEX_VERSION); + OutBuffer.WriteByte (0); + OutBuffer.WriteShort (htons (1024)); + SendResponse (); + +} + +void +IvlObexAgent :: ParseDisconnect (sword len, bool final) +{ + cerr << "--> disconnect.\n"; + InBuffer.Flush (); + OutBuffer.WriteByte (OBEX_SUCCESS_RSP | OBEX_FINAL_BIT); + OutBuffer.WriteShort (htons (3)); + SendResponse (); + OutSize = 255; +} + +void +IvlObexAgent :: ParseAbort (sword len, bool final) +{ + cerr << "--> abort.\n"; + InBuffer.Flush (); +} + +void +IvlObexAgent :: ParsePut (sword len, bool final) +{ + cerr << "--> put " << len << " bytes " << (final ? "!\n" : ".. \n"); + ParseHeaders (len, final); + byte rsp = final ? OBEX_SUCCESS_RSP : OBEX_CONTINUE_RSP; + OutBuffer.WriteByte (rsp | OBEX_FINAL_BIT); + OutBuffer.WriteShort (htons (3)); + SendResponse (); +} + +void +IvlObexAgent :: ParseGet (sword len, bool final) +{ + cerr << "--> get.\n"; + InBuffer.Flush (); +} + +void +IvlObexAgent :: ParseSetPath (sword len, bool final) +{ + cerr << "--> setpath.\n"; + InBuffer.Flush (); +} + +void +IvlObexAgent :: ParseHeaders (sword, bool) +{ + while (InBuffer.BufLength () > 0) { + byte hi; + InBuffer.ReadByte (hi); + + switch (hi & ~OBEX_HI_MASK) { + case OBEX_HEADER_COUNT: + cerr << "COUNT:\n"; + break; + case OBEX_HEADER_NAME: + cerr << "NAME:\n"; + break; + case OBEX_HEADER_TYPE: + cerr << "TYPE:\n"; + break; + case OBEX_HEADER_LENGTH: + cerr << "LENGTH:\n"; + break; + case OBEX_HEADER_TIME: + cerr << "TIME:\n"; + break; + case OBEX_HEADER_DESCRIPTION: + cerr << "DESCR:\n"; + break; + case OBEX_HEADER_TARGET: + cerr << "TARGET:\n"; + break; + case OBEX_HEADER_HTTP: + cerr << "HTTP:\n"; + break; + case OBEX_HEADER_BODY: + cerr << "BODY:\n"; + break; + case OBEX_HEADER_BODY_END: + cerr << "BODY END:\n"; + break; + case OBEX_HEADER_WHO: + cerr << "WHO:\n"; + break; + case OBEX_HEADER_CONNECTIONID: + cerr << "CONNECTIONID\n"; + break; + case OBEX_HEADER_PARAMS: + cerr << "PQRQMS\n"; + break; + case OBEX_HEADER_CHALLENGE: + cerr << "CHALLENGE\n"; + break; + case OBEX_HEADER_RESPONSE: + cerr << "RESPONSE:\n"; + break; + case OBEX_HEADER_CLASS: + cerr << "CLASS\n"; + break; + default: + cerr << "UNKNOWN:\n"; + } + switch (hi & OBEX_HI_MASK) { + case OBEX_UNICODE_HDR: + { + sword hlen; + InBuffer.ReadShort (hlen); + hlen = ntohs (hlen); + byte buf[hlen-3]; + InBuffer.ReadBuf (buf, hlen-3); + int l = hlen/2 + 1; + unsigned char str[l]; + unsigned char* p = str; + unsigned char* q = buf+1; + while (*p = *q++) { + p++; + q++; + } + cerr << "Unicode header, " << hlen - 3 << " bytes: \"" << str << "\"\n"; + break; + } + case OBEX_STRING_HDR: + { + sword hlen; + InBuffer.ReadShort (hlen); + hlen = ntohs (hlen); + byte buf[hlen-3+1]; // on ajoute un pour l'impression de debug + InBuffer.ReadBuf (buf, hlen-3); + buf[hlen-3] = '\0'; // pour l'impression de debug + cerr << "String header, " << hlen - 3 << " bytes: \"" << (char*) buf << "\"\n"; + break; + } + case OBEX_INT_HDR: + { + lword l; + InBuffer.ReadLong (l); + l = ntohl (l); + cerr << "Int-long header: 0x" << hex (l) << "\n"; + break; + } + case OBEX_BYTE_HDR: + { + byte b; + InBuffer.ReadByte (b); + cerr << "Char-long header: 0x" << hex (b) << "\n"; + break; + } + default: + cerr << "bad header 0x" << hex (hi) << "\n"; + + } + } + + /* if not correctly parsed object, then + deliver OBEX_BAD_REQUEST_RSP to peer + and deliver OBEX_EV_PARSEERR to upper soft layers*/ +} + +void +IvlObexAgent :: SendResponse () +{ + Flush (); +} + +void +IvlObexAgent :: SendHeader (byte hi, byte encoding, const char* hv) +{ + OutBuffer.WriteByte (hi | encoding); + + switch (encoding) { + case OBEX_UNICODE_HDR: + { + int len = 2 * (strlen (hv) + 1); + OutBuffer.WriteShort (htons (3 + len)); + byte buf[len]; + byte* p = buf; + while (*hv) { + *p++ = '\0'; + *p++ = *hv++; + } + *p++ = '\0'; + *p++ = '\0'; + OutBuffer.WriteBuf ((const byte*) buf, len); + break; + } + break; + case OBEX_STRING_HDR: + { + int len = strlen (hv) + 1; + OutBuffer.WriteShort (htons (3 + len)); + OutBuffer.WriteBuf ((const byte*) hv, len); + break; + } + case OBEX_BYTE_HDR: + OutBuffer.WriteByte ((byte) hv); + break; + case OBEX_INT_HDR: + OutBuffer.WriteLong (htonl ((lword) hv)); + break; + } +} + + +void +IvlObexAgent :: SendObject (IvlObexObject& o) +{ + /* faudrait d'abord envoyer la connexion ! */ + OutBuffer.WriteByte (OBEX_PUT_REQ | OBEX_FINAL_BIT); + int l = 3 + (1 + 4) + (3 + 2 * (strlen (o.Name) + 1)) + (3 + strlen (o.Body) + 1); + OutBuffer.WriteShort (htons (l));// FIXME!!!! + + if (o.Class) + SendHeader (OBEX_HEADER_CLASS, OBEX_INT_HDR, (const char*) o.Class); + + if (o.Name) + SendHeader (OBEX_HEADER_NAME, OBEX_UNICODE_HDR, o.Name); + + if (o.Description) + SendHeader (OBEX_HEADER_DESCRIPTION, OBEX_STRING_HDR, o.Description); + + if (o.Body) + SendHeader (OBEX_HEADER_BODY_END, OBEX_STRING_HDR, o.Body); + + +#if 0 + /* a faire a terme : decouper l'emission en plusieurs paquets, dont + un avec un BODY_END vide (cf doc page 29) */ + /* et puis aussi ajouter la deconnexion */ + + OutBuffer.WriteByte (OBEX_PUT_REQ | OBEX_FINAL_BIT); + OutBuffer.WriteShort (htons (3)); + SendHeader (OBEX_HEADER_BODY_END, 0); +#endif + SendResponse (); +} + + +IvlEventType* IvlObexAgentEvent::ObexNewAgent = new IvlEventType ("new agent", 0, 0); +IvlEventType* IvlObexAgentEvent::ObexAgentBye = new IvlEventType ("agent bye", 0, 0); + +IvlObexAgentEvent :: IvlObexAgentEvent (IvlObexAgent* a, IvlEventType* t) +: IvlEvent (t), + Agent (a) +{ +} + +IvlObexAgentEvent :: ~IvlObexAgentEvent () +{ +} + + +IvlObexObject :: IvlObexObject () +: Name (), + Description (), + Body (), + Class (0) +{ +} + +IvlObexObject :: ~IvlObexObject () +{ +} + +void +IvlObexObject :: SetName (const char* n) +{ + Name = n; +} + +void +IvlObexObject :: SetDescription (const char* d) +{ + Description = d; +} + +void +IvlObexObject :: SetBody (const char* b) +{ + Body = b; +} + +void +IvlObexObject :: SetClass (lword c) +{ + Class = c; +} + -- cgit v1.1