Author: ArcRiley Date: 2009-02-25 04:55:09 -0500 (Wed, 25 Feb 2009) New Revision: 1516
Modified: trunk/concordance/include/concordance.h trunk/concordance/include/concordance.sockets.h trunk/concordance/src/sockets/Client.c trunk/concordance/src/sockets/Socket.c Log: continued refactoring work Modified: trunk/concordance/include/concordance.h =================================================================== --- trunk/concordance/include/concordance.h 2009-02-22 22:27:53 UTC (rev 1515) +++ trunk/concordance/include/concordance.h 2009-02-25 09:55:09 UTC (rev 1516) @@ -44,4 +44,8 @@ GThread* threadid; /* thread ID for mainloop thread */ } concordGlobals; +typedef struct { + guint major; + guint minor; +} concordVersion; #endif Modified: trunk/concordance/include/concordance.sockets.h =================================================================== --- trunk/concordance/include/concordance.sockets.h 2009-02-22 22:27:53 UTC (rev 1515) +++ trunk/concordance/include/concordance.sockets.h 2009-02-25 09:55:09 UTC (rev 1516) @@ -28,7 +28,7 @@ /* concordance.sockets.Socket definition */ typedef struct { PyTypeObject base; - gboolean (* _listenNew ) (GIOChannel*, GIOCondition, gpointer); + gboolean (* _gioNew ) (GIOChannel*, GIOCondition, gpointer); } socketsSocket_TypeObject; extern socketsSocket_TypeObject socketsSocket_Type; typedef struct { @@ -45,7 +45,10 @@ /* concordance.sockets.Client definition */ typedef struct { PyTypeObject base; - gboolean (* _listenNew ) (GIOChannel*, GIOCondition, gpointer); + gboolean (* _gioNew ) (GIOChannel*, GIOCondition, gpointer); + gboolean (* _gioRead ) (GIOChannel*, GIOCondition, gpointer); + gboolean (* _gioWrite ) (GIOChannel*, GIOCondition, gpointer); + } socketsClient_TypeObject; extern socketsClient_TypeObject socketsClient_Type; typedef struct { @@ -59,4 +62,30 @@ } socketsClient_Object; +enum socketsClient_State { + CONCORD_E_CLOSE = 0, /* close </stream> */ + CONCORD_E_OPEN, /* inside <stream> */ + CONCORD_E_CLIENT, /* <iq> <message> <presence> */ + CONCORD_E_SASL, /* <auth> <response> */ +}; + + +typedef struct { + socketsClient_Object self; /* the listening socket this belongs */ + PyObject* node; /* Node object for this or NULL */ + GMutex* lock; /* Mutex for self test/add/remove */ + GIOChannel* chan; /* Glib IO channel for this session */ + Gsasl_session* sctx; /* gsasl session context */ + XML_Parser pars; /* expat parser for this session */ + gchar host[256]; /* verified hostname or NULL */ + concordVersion vers; /* XMPP major.minor version */ + gboolean tls; /* flag for whether tls is enabled */ + gint state; /* current session state */ + gint depth; /* parser element depth */ + GString* wbuff; /* write buffer */ + GString* ebuff; /* element buffer */ + GString* efrom; /* JID of origin */ + GString* eto; /* JID of destination */ +} socketsClient_Data; + #endif Modified: trunk/concordance/src/sockets/Client.c =================================================================== --- trunk/concordance/src/sockets/Client.c 2009-02-22 22:27:53 UTC (rev 1515) +++ trunk/concordance/src/sockets/Client.c 2009-02-25 09:55:09 UTC (rev 1516) @@ -25,14 +25,23 @@ cdef class Socket : \*/ static char socketsClient_Doc[] = "Test"; + /* + # + ########################################################################### + # + # GIOChannel Callbacks + # */ + static gboolean - _listenNew(GIOChannel* channel, GIOCondition condition, gpointer s) { /*\ + _gioNew(GIOChannel* channel, GIOCondition condition, gpointer s) { /*\ cdef : \*/ socketsSocket_Object* self = (socketsSocket_Object*) s; gint socket; struct sockaddr addr; guint addrlen = sizeof(addr); - + socketsClient_Data* session; + GSource* source; + /* accept new connection, return if we fail to connect int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); @@ -43,17 +52,674 @@ return TRUE; } - /* this is a generic Socket object, just close incoming connections */ - close(socket); + /* create new session, create lock, and store the pointer to parent Core + + gpointer g_malloc (gsize n_bytes); + GMutex * g_mutex_new (); + */ + session = g_malloc(sizeof(socketsClient_Data)); + session->self = self; + session->node = NULL; + session->lock = g_mutex_new(); + + /* create a new channel using an OS-dependent function + + GIOChannel* g_io_channel_win32_new_fd (gint fd); + GIOChannel* g_io_channel_unix_new (gint fd); + */ + #ifdef MS_WINDOWS + session->chan = g_io_channel_win32_new_socket(socket); + #else + session->chan = g_io_channel_unix_new(socket); + #endif + + /* add a channel to our context watch + + Note that this function is a replacement for g_io_add_watch() which + uses our own context rather than the default context. + + GSource* g_io_create_watch (GIOChannel *channel, + GIOCondition condition); + void g_source_set_callback (GSource *source, + GSourceFunc func, + gpointer data, + GDestroyNotify notify); + guint g_source_attach (GSource *source, + GMainContext *context); + */ + source = g_io_create_watch(session->chan, G_IO_IN | G_IO_HUP); + g_source_set_callback(source, (GSourceFunc) + ((socketsClient_TypeObject*) s->ob_type)->_gioRead, + (gpointer) session, NULL); + g_source_attach(source, self->context); + g_source_unref(source); + + /* specify "raw" channel by turning off encoding and buffering + + GIOStatus g_io_channel_set_encoding(GIOChannel *channel, + const gchar *encoding, + GError **error); + void g_io_channel_set_buffered(GIOChannel *channel, + gboolean buffered); + */ + g_io_channel_set_encoding(session->chan, NULL, NULL); + g_io_channel_set_buffered(session->chan, FALSE); + + /* initialize session strings + + GString* g_string_new (const gchar *init); + */ + session->wbuff = g_string_new(""); /* write buffer */ + session->ebuff = g_string_new(""); /* element buffer */ + session->efrom = g_string_new(""); /* element origin */ + session->eto = g_string_new(""); /* element destination */ + + /* initialize session state */ + session->state = CONCORD_E_OPEN; + session->depth = 0; + + /* create the XML parser for this session + + XML_Parser XML_ParserCreateNS (const XML_Char *encoding + XML_Char sep); + void XML_SetUserData (XML_Parser parser, + void *userData); + void XML_SetElementHandler (XML_Parser parser, + XML_StartElementHandler start, + XML_EndElementHandler end); + void XML_SetCharacterDataHandler + (XML_Parser parser, + XML_CharacterDataHandler); + */ + session->pars = XML_ParserCreateNS(NULL, 32); /* space is separator*/ + XML_SetUserData(session->pars, session); + XML_SetElementHandler(session->pars, _xmlStart, _xmlEnd); + XML_SetCharacterDataHandler(session->pars, _xmlCharData); + + /* lookup hostname - currently unimplemented */ /* return true so GMainLoop will continue watching this channel */ return TRUE; } + static gboolean + _gioRead(GIOChannel* channel, GIOCondition condition, gpointer data) { /*\ + cdef : \*/ + socketsClient_Data* session = (socketsClient_Data*) data; + GIOStatus status; + gchar buff[4096]; + guint buff_len = 0; + + if (condition == G_IO_IN) { + + /* read channel to parser + + GIOStatus g_io_channel_read_chars (GIOChannel *channel, + gchar *buf, + gsize count, + gsize *bytes_read, + GError **error); + XML_Status XML_Parse (XML_Parser parser, + const char *s, + int len, + int isFinal); + */ + status = g_io_channel_read_chars(channel, buff, 4096, &buff_len, NULL); + if (status == G_IO_STATUS_NORMAL && buff_len > 0) { + /* parse XML data + + XML_Parse will execute the appropriate callback(s) found in the + "XML Handlers" section below. If the stream has been closed + the session->state will be reset to CONCORD_E_CLOSE (== FALSE) + to indicate that the session needs to get wrapped up. + */ + if (XML_Parse(session->pars, buff, buff_len, FALSE) && session->state) + /* return true so GMainLoop will continue watching this channel */ + return TRUE; + } + } + + /* G_IO_EOF, read EOF, or an unknown channel error has occured + + GIOStatus g_io_channel_shutdown (GIOChannel *channel, + gboolean flush, + GError **err); + */ + g_io_channel_shutdown(channel, FALSE, NULL); + + + /* close out parser cleanly + + void XML_ParserFree (XML_Parser parser); + */ + XML_Parse(session->pars, NULL, 0, TRUE); + XML_ParserFree(session->pars); + + + /* return false so GMainLoop will stop watching this channel */ + return FALSE; + } + + + static gboolean + _gioWrite(GIOChannel* channel, GIOCondition condition, gpointer data) { /*\ + cdef : \*/ + socketsClient_Data* session = (socketsClient_Data*) data; + gint sent; + + /* send as much data as the channel will take + + GIOStatus g_io_channel_write_chars (GIOChannel *channel, + const gchar *buf, + gssize count, + gsize *bytes_written, + GError **error); + */ + g_io_channel_write_chars(channel, session->wbuff->str, session->wbuff->len, + &sent, NULL); + + /* erase what we've just written + + GString* g_string_erase (GString *string, + gssize pos, + gssize len); + */ + session->wbuff = g_string_erase(session->wbuff, 0, sent); + + /* keep this event alive if there's more in the buffer to write */ + return (session->wbuff->len != 0); + } + /* # ########################################################################### # + # Element responses + # */ + static void + _reply_streamError(socketsClient_Data* session, gchar* err, gchar* t) { /*\ + cdef : \*/ + GString* buff; + + /* initialize response buffer + + GString* g_string_new (const gchar *init); + */ + buff = g_string_new(""); + + /* printf error and send it + + void g_string_printf (GString *string, + const gchar *format, + ...); + */ + g_string_printf(buff, + "<stream:error><%s" + " xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>" + "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>" + "%s</text></stream:error></stream:stream>", + err, t); + _send(session, buff->str, buff->len); + + /* CLOSE the session state so the socket will close properly */ + session->state = CONCORD_E_CLOSE; + + /* free response buffer before returning + + gchar* g_string_free (GString *string, + gboolean free_segment); + */ + g_string_free(buff, TRUE); + } + + + static void + _reply_stream(concordSession_Data* session, gchar* version) { /*\ + cdef : \*/ + socketsClient_Object* self = session->self; + GString* buff; + guint major, minor; + gchar** versions; + gchar* error = NULL; + + + /* initialize response buffer + + GString* g_string_new (const gchar *init); + */ + buff = g_string_new(""); + + /* init <stream:stream>, <stream:features>, and <mechanisms> + + void g_string_printf (GString *string, + const gchar *format, + ...); + */ + g_string_printf(buff, + "<?xml version='1.0' encoding='UTF-8'?>" + "<stream:stream xmlns='jabber:client'" + " xmlns:stream='http://etherx.jabber.org/streams'" + " from='%s' id='%s'", + "selket.apogean.org", "concordance-1"); + + /* Parse version: + * NULL is passed, meaning the version attribute was not found, so: + * version is "" + * error is "<unsupported-version/>" + + * "1.*" is passed, meaning a version we support, so: + * version is " version='1.0'" + * error is NULL + + * "2.0" or greater is passed, meaning a version we do not support, so: + * version is " version='1.0'" + * error is "<unsupported-version/>" + + * something unparsable is passed, so: + * version is " version='1.0'" + * error is "<bad-format/>" + */ + if (version == NULL) { + *error = "<unsupported-version/>"; + buff = g_string_append(buff, ">"); + } + else if (g_strrstr(version, ".") != NULL) { + versions = g_strsplit(version, ".", 2); // Separate Major from minor + if (g_strv_length(versions) == 2 && + concordStrToUI(versions[0], &major) && + concordStrToUI(versions[1], &minor)) { + g_strfreev(versions); + if (major == 1) { + buff = g_string_append(buff, " version='1.0'>"); + + } + else { + *error = "<unsupported-version/>"; + buff = g_string_append(buff, " version='1.0'>"); + } + } + g_strfreev(versions); + } + else { + *error = "<bad-format/>"; + buff = g_string_append(buff, " version='1.0'>"); + } + + if (error) { + /* a stream error has taken place! + + send the <stream:stream> element we have so far, free the buffer, + then pass control over concordSession_reply_streamError for the rest. + */ + _send(session, buff->str, buff->len); + g_string_free(buff, TRUE); + _reply_streamError(session, error, ""); + return; + } + + /* open stream:features and mechanisms element */ + buff = g_string_append(buff, "<stream:features><mechanisms" + " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"); + + /* add a <mechanism> element for each supported SASL mechanism + + int gsasl_server_support_p (Gsasl* ctx, + const char* name); + GString* g_string_append (GString *string, + const gchar *val); + */ + if (gsasl_server_support_p(core->saslCntx, "DIGEST-MD5")) + buff = g_string_append(buff, "<mechanism>DIGEST-MD5</mechanism>"); + if (gsasl_server_support_p(core->saslCntx, "PLAIN")) + buff = g_string_append(buff, "<mechanism>PLAIN</mechanism>"); + + /* close <mechanisms> and <stream:features>, then send buffer */ + buff = g_string_append(buff, "</mechanisms></stream:features>"); + _send(session, buff->str, buff->len); + + /* free response buffer before returning + + gchar* g_string_free (GString *string, + gboolean free_segment); + */ + g_string_free(buff, TRUE); + } + + + static void + _reply_saslChallenge(concordSession_Data* session) { /*\ + cdef : \*/ + + + return; + /* + gchar* g_base64_encode (const guchar *data, + gsize len); + */ + + + + /*g_string_printf(buff, + "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + "%s</challenge>", ""); */ + } + + static void + _reply_saslFailure(socketsClient_Data* session, gchar* failure) { /*\ + cdef : \*/ + GString* buff; + + /* printf failure to a GString, then send it + + GString* g_string_new (const gchar *init); + void g_string_printf (GString *string, + const gchar *format, + ...); + gchar* g_string_free (GString *string, + gboolean free_segment); + */ + buff = g_string_new(""); + g_string_printf(buff, "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + "%s</failure>", failure); + concordSession_send(session, buff->str, buff->len); + g_string_free(buff, TRUE); + } + + /* + # + ########################################################################### + # + # XML Callbacks + # + */ + static gchar* + _xmlFindAttr(const gchar** attrs, const gchar* key) { /*\ + cdef : \*/ + gint i; + + for (i = 0; attrs[i]; i += 2) { + /* compare attribute strings case insensitive + + gint g_ascii_strcasecmp (const gchar *s1, + const gchar *s2); + */ + if (g_ascii_strcasecmp(attrs[i], key) == 0) + break; + } + + /* return either the found attribute value or NULL */ + if (attrs[i]) + return (gchar*) attrs[i+1]; + else + return NULL; + } + + static void + _xmlStart(gpointer data, const XML_Char* name, const XML_Char** atts) { /*\ + cdef : \*/ + socketsClient_Data* session = (socketsClient_Data*) data; + gchar** element; + gint i; + gchar* attr; + + /* split name by namespace and element + + gchar** g_strsplit (const gchar *string, + const gchar *delimiter, + gint max_tokens); + */ + element = g_strsplit(name, " ", 2); + + /* this from http://www.xml.com/pub/a/1999/09/expat/index.html?page=2 */ + for (i = 0; i < session->depth; i++) + printf(" "); + + printf("<%s xmlns='%s'", element[1], element[0]); + for (i = 0; atts[i]; i += 2) + printf(" %s='%s'", atts[i], atts[i + 1]); + printf(">\n"); + + switch (session->depth) { + /* match element by depth, namespace, and finally element name + + gint g_ascii_strcasecmp (const gchar* str1, + const gchar* str2); + Returns -1, 0 or 1, if str1 is <, == or > than str2. + */ + case 0 : { + /* only stream:stream element is valid at depth == 0 + + if anything else is sent, session->state will be changed from + CONCORD_E_OPEN to CONCORD_E_CLOSE and the session will be closed + in the _gioRead function just after the XMLParse call (below). + */ + if (g_ascii_strcasecmp(name, + "http://etherx.jabber.org/streams stream") == 0) + _reply_stream(session, _xmlFindAttr(atts, "version")); + else + session->state = CONCORD_E_CLOSE; + break; + } + + case 1 : { + if (g_ascii_strcasecmp(element[0], "jabber:client") == 0) { + /* ensure it's a valid element for the jabber:client namespace + + see http://xmpp.org/rfcs/rfc3920.html#def + */ + if (g_ascii_strcasecmp(element[1], "iq") == 0 || + g_ascii_strcasecmp(element[1], "message") == 0 || + g_ascii_strcasecmp(element[1], "presence") == 0) { + /* set the client mode */ + session->state = CONCORD_E_CLIENT; + + /* check and copy "to" and "from" attributes + + GString* g_string_assign (GString *string, + const gchar *rval); + */ + if (!(attr = _xmlFindAttr(atts, "to"))) + attr = ""; + session->eto = g_string_assign(session->eto, attr); + if (!(attr = _xmlFindAttr(atts, "from"))) + attr = ""; + session->efrom = g_string_assign(session->efrom, attr); + printf("--------- from: %s to: %s\n", + session->efrom->str, session->eto->str); + + /* copy element and attributes to ebuff + + void g_string_printf (GString *string, + const gchar *format, + ...); + void g_string_append_printf (GString *string, + const gchar *format, + ...); + */ + g_string_printf(session->ebuff, "<%s xmlns='%s'", + element[1], element[0]); + for (i = 0; atts[i]; i += 2) + g_string_append_printf(session->ebuff, " %s='%s'", + atts[i], atts[i+1]); + g_string_append_printf(session->ebuff, ">"); + } + else + _reply_streamError(session, "invalid-xml", + "invalid element for jabber:client namespace"); + } + if (g_ascii_strcasecmp(element[0], + "urn:ietf:params:xml:ns:xmpp-sasl") == 0) { + if (g_ascii_strcasecmp(element[1], "auth") == 0) { + gchar* mec = _xmlFindAttr(atts, "mechanism"); + if (mec && gsasl_server_support_p(session->core->saslCntx, mec)) { + /* initiate server session + + int gsasl_server_start (Gsasl* ctx, + const char* mech, + Gsasl_session** sctx); + */ + printf("------ init %s mechanism\n", mec); + gsasl_server_start(session->core->saslCntx, mec, + &session->sctx); + session->state = CONCORD_E_SASL; + } + else { + _reply_saslFailure(session, "<invalid-mechanism/>"); + session->state = CONCORD_E_CLOSE; + } + } + + /* remaining: response, abort */ + } + + /* other depth=1 namespaces */ + break; + } + + default : { + /* process inner tags depending on state */ + if (session->state == CONCORD_E_CLIENT) { + /* copy element and attributes to ebuff + + void g_string_append_printf (GString *string, + const gchar *format, + ...); + */ + g_string_append_printf(session->ebuff, "<%s xmlns='%s'", + element[1], element[0]); + for (i = 0; atts[i]; i += 2) + g_string_append_printf(session->ebuff, " %s='%s'", + atts[i], atts[i+1]); + g_string_append_printf(session->ebuff, ">"); + } + break; + } + } + + /* increase XML depth regardless of element */ + session->depth++; + + /* free the element string array + + void g_strfreev (gchar **str_array); + */ + g_strfreev(element); + } + + + static void + concordSession_xmlEnd(gpointer data, const XML_Char* name) { /*\ + cdef : \*/ + concordSession_Data* session = (concordSession_Data*) data; + gchar** element; + gint i; + + /* decrease XML depth */ + session->depth--; + + + /* split name by namespace and element + + gchar** g_strsplit (const gchar *string, + const gchar *delimiter, + gint max_tokens); + */ + element = g_strsplit(name, " ", 2); + + for (i = 0; i < session->depth; i++) + printf(" "); + printf("</%s>\n", element[1]); + + switch (session->depth) { + case 0 : { + /* close session when </stream:stream> is sent */ + session->state = CONCORD_E_CLOSE; + break; + } + case 1 : { + switch (session->state) { + case CONCORD_E_CLIENT : { + /* copy final element close to ebuff + + void g_string_append_printf (GString *string, + const gchar *format, + ...); + */ + g_string_append_printf(session->ebuff, "</%s>", element[1]); + concordCore_queuePush(session->core, session); + break; + } + case CONCORD_E_SASL : { + concordSession_reply_saslChallenge(session); + break; + } + } + + /* reset the element strings when returning to depth 1 + + GString* g_string_assign (GString *string, + const gchar *rval); + */ + session->ebuff = g_string_assign(session->ebuff, ""); + session->efrom = g_string_assign(session->efrom, ""); + session->eto = g_string_assign(session->eto, ""); + + break; + } + default : { + if (session->state == CONCORD_E_CLIENT) { + /* copy element close to ebuff + + void g_string_append_printf (GString *string, + const gchar *format, + ...); + */ + g_string_append_printf(session->ebuff, "</%s>", element[1]); + } + break; + } + } + + /* free the element string array before returning + + void g_strfreev (gchar **str_array); + */ + g_strfreev(element); + } + + + static void + concordSession_xmlCharData(gpointer data, const XML_Char* str, + gint str_len) { /*\ + cdef : \*/ + concordSession_Data* session = (concordSession_Data*) data; + gint i; + gchar* strn; + + /* output to stdout for debugging */ + strn = g_strndup(str, str_len); + for (i = 0; i < session->depth; i++) + printf(" "); + printf("\"%s\"\n", strn); + g_free(strn); + + if (session->state == CONCORD_E_OPEN) + /* character data should be ignored at the stream level */ + return; + + /* append character data to element buffer + + GString* g_string_append_len (GString *string, + const gchar *val, + gssize len); + */ + session->ebuff = g_string_append_len(session->ebuff, str, str_len); + } + + /* + # + ########################################################################### + # # object constructor - deconstructor # */ @@ -155,5 +821,7 @@ 0, /*tp_is_gc*/ }, /* cdef classes */ - _listenNew, + _gioNew, + _gioRead, + _gioWrite, }; Modified: trunk/concordance/src/sockets/Socket.c =================================================================== --- trunk/concordance/src/sockets/Socket.c 2009-02-22 22:27:53 UTC (rev 1515) +++ trunk/concordance/src/sockets/Socket.c 2009-02-25 09:55:09 UTC (rev 1516) @@ -21,9 +21,6 @@ #include "concordance.sockets.h" -static gboolean -_listenNew(GIOChannel* channel, GIOCondition condition, gpointer s); - /*\ cdef class Socket : \*/ static char socketsSocket_Doc[] = "Test"; @@ -174,7 +171,7 @@ source = g_io_create_watch(self->chan, G_IO_IN); g_source_set_callback(source, (GSourceFunc) ((socketsSocket_TypeObject*) s->ob_type)-> - _listenNew, + _gioNew, (gpointer) self, NULL); g_source_attach(source, self->context); g_source_unref(source); @@ -213,7 +210,7 @@ } static gboolean - _listenNew(GIOChannel* channel, GIOCondition condition, gpointer s) { /*\ + _gioNew(GIOChannel* channel, GIOCondition condition, gpointer s) { /*\ cdef : \*/ socketsSocket_Object* self = (socketsSocket_Object*) s; gint socket; @@ -363,7 +360,7 @@ 0, /*tp_is_gc*/ }, /* cdef classes */ - _listenNew, + _gioNew, }; _______________________________________________ PySoy-SVN mailing list PySoy-SVN@pysoy.org http://www.pysoy.org/mailman/listinfo/pysoy-svn