Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members  

TelnetChunker.cxx

00001 /*
00002  * Copyright (C) 1991-9 Eric M. Hopper <hopper@omnifarious.mn.org>
00003  * 
00004  *     This program is free software; you can redistribute it and/or modify it
00005  *     under the terms of the GNU Lesser General Public License as published
00006  *     by the Free Software Foundation; either version 2 of the License, or
00007  *     (at your option) any later version.
00008  * 
00009  *     This program is distributed in the hope that it will be useful, but
00010  *     WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  *     Lesser General Public License for more details.
00013  * 
00014  *     You should have received a copy of the GNU Lesser General Public
00015  *     License along with this program; if not, write to the Free Software
00016  *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00017  */
00018 
00019 /* $Header: /home/hopper/src/cvs/C++/StrMod/TelnetChunker.cxx,v 1.7 2002/08/29 00:58:04 hopper Exp $ */
00020 
00021 // For a log, see ChangeLog
00022 
00023 #ifdef __GNUG__
00024 #  pragma implementation "TelnetChunker.h"
00025 #endif
00026 
00027 #include "StrMod/TelnetChunker.h"
00028 
00029 #ifdef __GNUG__
00030 #  pragma implementation "TelnetChunkerData.h"
00031 #endif
00032 
00033 #include "StrMod/TelnetChunkerData.h"
00034 #include "StrMod/TelnetParser.h"
00035 #include "StrMod/TelnetChunkBuilder.h"
00036 #include "StrMod/StrSubChunk.h"
00037 #include "StrMod/GroupChunk.h"
00038 #include "StrMod/PreAllocBuffer.h"
00039 #include "StrMod/DynamicBuffer.h"
00040 #include "StrMod/ApplyVisitor.h"
00041 #include "StrMod/StrChunkPtrT.h"
00042 #include "StrMod/StrChunkPtr.h"
00043 #include <cassert>
00044 #include <deque>
00045 
00046 namespace strmod {
00047 namespace strmod {
00048 
00049 using lcore::U1Byte;
00050 
00051 const STR_ClassIdent TelnetChunker::identifier(33UL);
00052 const STR_ClassIdent TelnetChunker::TelnetData::identifier(34UL);
00053 const STR_ClassIdent TelnetChunker::SingleChar::identifier(35UL);
00054 const STR_ClassIdent TelnetChunker::Suboption::identifier(36UL);
00055 const STR_ClassIdent TelnetChunker::OptionNegotiation::identifier(37UL);
00056 
00057 const U1Byte TelnetChunker::Suboption::optend_[2] = {
00058    TelnetChars::IAC, TelnetChars::SE
00059 };
00060 
00061 class TelnetChunker::Builder : public TelnetChunkBuilder {
00062  private:
00063    static const int qsize_ = 16;
00064  public:
00065    Builder() : curlen_(0)                                   { }
00066    virtual ~Builder()                                       { }
00067 
00068    virtual void addDataBlock(size_t regionbegin, size_t regionend);
00069    virtual void addCharCommand(TelnetChars::Commands command);
00070    virtual void addNegotiationCommand(TelnetChars::OptionNegotiations negtype,
00071                                       U1Byte opt_type);
00072    virtual void addSuboption(U1Byte opt_type,
00073                              size_t regionbegin, size_t regionend,
00074                              StrChunkPtrT<BufferChunk> &cooked);
00075 
00076    void addOutgoing(const StrChunkPtr &ptr);
00077 
00078    void addIncoming(const StrChunkPtr &ptr);
00079    size_t curIncomingLen() const                            { return curlen_; }
00080    inline void clearIncoming();
00081    inline void setIncoming(const StrChunkPtr &ptr);
00082    inline void setIncoming(const StrChunkPtr &ptr, size_t inlen);
00083 
00084    inline bool hasData() const;
00085    const StrChunkPtr getFromQueue();
00086 
00087  private:
00088    std::deque<StrChunk *> chunks_;
00089    StrChunkPtr curchunk_;
00090    size_t curlen_;
00091 
00092    inline StrChunk *makeDataPtr(size_t regionbegin, size_t regionend);
00093 };
00094 
00095 inline void TelnetChunker::Builder::clearIncoming()
00096 {
00097    curchunk_.ReleasePtr();
00098    curlen_ = 0;
00099 }
00100 
00101 inline void TelnetChunker::Builder::setIncoming(const StrChunkPtr &ptr)
00102 {
00103    setIncoming(ptr, ptr->Length());
00104 }
00105 
00106 inline void
00107 TelnetChunker::Builder::setIncoming(const StrChunkPtr &ptr, size_t inlen)
00108 {
00109    curchunk_ = ptr;
00110    curlen_ = inlen;
00111 }
00112 
00113 inline bool TelnetChunker::Builder::hasData() const
00114 {
00115    return !chunks_.empty();
00116 }
00117 
00118 inline StrChunk *
00119 TelnetChunker::Builder::makeDataPtr(size_t regionbegin, size_t regionend)
00120 {
00121    if ((regionbegin == 0) && (regionend == curlen_))
00122    {
00123       return curchunk_.GetPtr();
00124    }
00125    else
00126    {
00127       return new StrSubChunk(curchunk_,
00128                              LinearExtent(regionbegin,
00129                                           regionend - regionbegin));
00130    }
00131 }
00132 
00133 void TelnetChunker::Builder::addDataBlock(size_t regionbegin, size_t regionend)
00134 {
00135    assert(curchunk_);
00136    assert(regionend >= regionbegin);
00137    assert(regionbegin <= curlen_);
00138    assert(regionend <= curlen_);
00139    if (regionend == regionbegin)
00140    {
00141       return;
00142    }
00143    StrChunk *ptr = makeDataPtr(regionbegin, regionend);
00144    ptr->AddReference();
00145    chunks_.push_back(ptr);
00146 }
00147 
00148 void TelnetChunker::Builder::addCharCommand(TelnetChars::Commands command)
00149 {
00150    StrChunk *ptr = new SingleChar(command);
00151    ptr->AddReference();
00152    chunks_.push_back(ptr);
00153 }
00154 
00155 void TelnetChunker::Builder::addNegotiationCommand(
00156    TelnetChars::OptionNegotiations negtype, U1Byte opt_type)
00157 {
00158    StrChunk *ptr = new OptionNegotiation(negtype, opt_type);
00159    ptr->AddReference();
00160    chunks_.push_back(ptr);
00161 }
00162 
00163 void TelnetChunker::Builder::addSuboption(U1Byte opt_type,
00164                                           size_t regionbegin, size_t regionend,
00165                                           StrChunkPtrT<BufferChunk> &cooked)
00166 {
00167    assert(curchunk_);
00168    assert(regionend > regionbegin);
00169    assert(regionbegin < curlen_);
00170    assert(regionend <= curlen_);
00171    StrChunk *data = 0;
00172    if (regionend > regionbegin)
00173    {
00174       data = makeDataPtr(regionbegin, regionend);
00175    }
00176    StrChunk *ptr = new Suboption(opt_type, cooked, data);
00177    ptr->AddReference();
00178    chunks_.push_back(ptr);
00179 }
00180 
00181 void TelnetChunker::Builder::addOutgoing(const StrChunkPtr &ptr)
00182 {
00183    StrChunk *rawptr = ptr.GetPtr();
00184    rawptr->AddReference();
00185    chunks_.push_back(rawptr);
00186 }
00187 
00188 void TelnetChunker::Builder::addIncoming(const StrChunkPtr &ptr)
00189 {
00190    const size_t inlen = ptr->Length();
00191    const size_t oldlen = curlen_;
00192    assert(inlen > 0);
00193    if (inlen == 0)
00194    {
00195       return;
00196    }
00197    assert((curchunk_ && (curlen_ > 0)) || (!curchunk_ && (curlen_ == 0)));
00198    if (!curchunk_)
00199    {
00200       setIncoming(ptr, inlen);  // Also sets curlen_
00201    }
00202    else
00203    {
00204       StrChunkPtrT<GroupChunk> gc;
00205       if ((curchunk_->NumReferences() == 1) &&
00206           curchunk_->AreYouA(GroupChunk::identifier))
00207       {
00208          gc = static_cast<GroupChunk *>(curchunk_.GetPtr());
00209       }
00210       else
00211       {
00212          GroupChunk *tmp = new GroupChunk;
00213          tmp->push_back(curchunk_);
00214          gc = tmp;
00215          curchunk_ = tmp;
00216       }
00217       gc->push_back(ptr);
00218       curlen_ += inlen;
00219       assert(curlen_ == curchunk_->Length());
00220    }
00221    assert(curlen_ == (oldlen + inlen));
00222 }
00223 
00224 const StrChunkPtr TelnetChunker::Builder::getFromQueue()
00225 {
00226    assert(!chunks_.empty());
00227    if (!chunks_.empty())
00228    {
00229       StrChunk *ptr = chunks_.front();
00230       chunks_.pop_front();
00231       assert(ptr->NumReferences() > 0);
00232       ptr->DelReference();
00233       return(ptr);
00234    }
00235    else
00236    {
00237       return 0;
00238    }
00239 }
00240 
00241 struct TelnetChunker::Internals {
00242    TelnetParser parser_;
00243    Builder builder_;
00244 };
00245 
00246 TelnetChunker::TelnetChunker()
00247      : data_(*(new Internals))
00248 {
00249 }
00250 
00251 TelnetChunker::~TelnetChunker()
00252 {
00253    delete &data_;
00254 }
00255 
00256 class TelnetChunker::DataFunctor {
00257  public:
00258    DataFunctor(TelnetParser &parser, TelnetChunkBuilder &builder)
00259         : parser_(parser), builder_(builder)
00260    {
00261    }
00262    void operator ()(const void *data, size_t len) const {
00263       parser_.processData(data, len, builder_);
00264    }
00265 
00266    TelnetParser &parser_;
00267    TelnetChunkBuilder &builder_;
00268 };
00269 
00270 void TelnetChunker::processIncoming()
00271 {
00272    assert(incoming_);
00273    const size_t inlen = incoming_->Length();
00274 
00275    if (!data_.builder_.hasData() && (inlen <= 0))
00276    {
00277       if (data_.parser_.getRegionBegin() != data_.builder_.curIncomingLen())
00278       {
00279          data_.parser_.reset(data_.builder_);
00280          assert(data_.parser_.getRegionBegin() ==
00281                 data_.builder_.curIncomingLen());
00282       }
00283       data_.parser_.dropBytes(data_.parser_.getRegionBegin());
00284       data_.builder_.clearIncoming();
00285       data_.builder_.addOutgoing(incoming_);
00286    }
00287    else if (!data_.builder_.hasData())
00288    {
00289       data_.builder_.addIncoming(incoming_);
00290       {
00291          DataFunctor tmp(data_.parser_, data_.builder_);
00292          for_each(incoming_, tmp);
00293       }
00294       data_.parser_.endOfChunk(data_.builder_);
00295 
00296       const size_t regionbegin = data_.parser_.getRegionBegin();
00297       const size_t totallen = data_.builder_.curIncomingLen();
00298       assert(regionbegin <= totallen);
00299 
00300       // If our parse buffer has gotten too huge (perhaps because of a
00301       // neverending suboption) reset the parser.
00302       if ((totallen - regionbegin) > MAX_SUBOPTSIZE)
00303       {
00304          data_.parser_.reset(data_.builder_);
00305          assert(data_.parser_.getRegionBegin() == totallen);
00306          data_.parser_.dropBytes(totallen);
00307          data_.builder_.clearIncoming();
00308          if (!data_.builder_.hasData())
00309          {
00310             incoming_.ReleasePtr();
00311             // Yeah, a bit ugly, but I think cleaner than adding a flag.
00312             return;
00313          }
00314       }
00315       // Otherwise, if everything has been completely parsed, drop it all.
00316       else if (regionbegin == totallen)
00317       {
00318          // Drop the uneeded blocks.
00319          data_.parser_.dropBytes(totallen);
00320          data_.builder_.clearIncoming();
00321       }
00322       else
00323       {
00324          assert(totallen >= inlen);
00325          // If the parser doesn't forsee a region starting before the beginning
00326          // of the block we just processed, drop all prior blocks.
00327          if ((regionbegin != 0) && ((totallen - regionbegin) <= inlen))
00328          {
00329             data_.parser_.dropBytes(totallen - inlen);
00330             data_.builder_.setIncoming(incoming_, inlen);
00331          }
00332       }
00333    }
00334    assert(data_.builder_.hasData());;
00335    if (data_.builder_.hasData())
00336    {
00337       outgoing_ = data_.builder_.getFromQueue();
00338       outgoing_ready_ = true;
00339    }
00340    if (!data_.builder_.hasData())
00341    {
00342       incoming_.ReleasePtr();
00343    }
00344 }
00345 
00346 void TelnetChunker::SingleChar::acceptVisitor(ChunkVisitor &visitor)
00347    throw(ChunkVisitor::halt_visitation)
00348 {
00349    call_visitDataBlock(visitor, buf_, 2);
00350 }
00351 
00352 TelnetChunker::Suboption::Suboption(U1Byte type,
00353                                     const StrChunkPtrT<BufferChunk> &cooked)
00354      : raw_(cookedToRaw(cooked)), rawlen_(raw_->Length()), cooked_(cooked)
00355 {
00356    optstart_[0] = TelnetChars::IAC;
00357    optstart_[1] = TelnetChars::SB;
00358    optstart_[2] = type;
00359 }
00360 
00361 const StrChunkPtr
00362 TelnetChunker::Suboption::cookedToRaw(const StrChunkPtrT<BufferChunk> &cooked)
00363 {
00364    const U1Byte * const cookedbuf = cooked->getCharP();
00365    const size_t cookedlen = cooked->Length();
00366    unsigned int countIAC = 0;
00367 
00368    for (size_t i = 0; i < cookedlen; ++i)
00369    {
00370       if (cookedbuf[i] == TelnetChars::IAC)
00371       {
00372          ++countIAC;
00373       }
00374    }
00375    if (countIAC == 0)
00376    {
00377       return(cooked);
00378    }
00379    else
00380    {
00381       // Every IAC currently in cooked will be replaced with IAC IAC.
00382       size_t rawlen = cooked->Length() + countIAC;
00383       BufferChunk *raw = 0;
00384       if (rawlen <= 16)
00385       {
00386          raw = new PreAllocBuffer<16>;
00387          raw->resize(rawlen);
00388       }
00389       else
00390       {
00391          raw = new DynamicBuffer(rawlen);
00392       }
00393       {
00394          U1Byte * const rawbuf = raw->getCharP();
00395          size_t o = 0;
00396 
00397          for (size_t i = 0; i < cookedlen; ++i)
00398          {
00399             U1Byte curchar = cookedbuf[i];
00400             if (curchar == TelnetChars::IAC)
00401             {
00402                rawbuf[o++] = curchar;
00403                rawbuf[o++] = curchar;
00404             }
00405             else
00406             {
00407                rawbuf[o++] = curchar;
00408             }
00409          }
00410          assert(o == rawlen);
00411       }
00412       return(raw);
00413    }
00414 }
00415 
00416 void TelnetChunker::Suboption::acceptVisitor(ChunkVisitor &visitor)
00417    throw(ChunkVisitor::halt_visitation)
00418 {
00419    call_visitDataBlock(visitor, optstart_, 3);
00420    call_visitStrChunk(visitor, raw_);
00421    call_visitDataBlock(visitor, const_cast<unsigned char *>(optend_), 2);
00422 }
00423 
00424 void TelnetChunker::OptionNegotiation::acceptVisitor(ChunkVisitor &visitor)
00425    throw(ChunkVisitor::halt_visitation)
00426 {
00427    call_visitDataBlock(visitor, buf_, 3);
00428 }
00429 
00430 };  // End namespace strmod
00431 };  // End namespace strmod

Generated on Wed Jan 29 00:32:44 2003 for libNet by doxygen1.3-rc1