Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 60
Текст из файла (страница 60)
Normallythe data would be passed on up the stack by calling NewData() on thesink.To deal with the fact that packets can be fragmented and arrive inpieces, the stencil may not be able to fully parse the raw data passedto it when NewData() is called on it. However, when it has collected13en.wikipedia.org/wiki/Endianess .you can’t avoid copying, for instance, when the raw data is stored innon-contiguous memory or you have to send it to another process over IPC.14 SometimesDATA PRESSCSourceCDataPressCStencil315RawDataCSinkNewData(CRawData*)NewData(CRawData*)Adopt()PacketComplete()Data is collected by thestencil until it hasenough to form a packetNewData(CRawData*)NewData(CRawData*)Adopt()PacketComplete()[packet complete]:Process()PacketType()HandlePacketTypeL()MpePacketTypeEvent(CStencil&)ValidatePacketTypeL()The exact functioncalled here depends onthe packet typeTransferPayload()NewData(CRawData*)Reset()Figure 8.5 Dynamics of the Data Press pattern (valid packet)enough data it begins its main task of parsing the packet in the followingstages:1.
The packet is validated according to what type it is. At the veryleast, you should check whether the packet is of the correct size butyou may need to perform more complex checks such as calculatingchecksums, etc.2. The specific packet type needs to be processed. For some packettypes this might be very simple but it can be much more complicatedand, for instance, involve transforming the data by decompressing ordecrypting it.3. It informs the data press, through the MPacketEvents event mixin,of the type of packet that has been processed.Once the data press has been notified it should then deal with theevent appropriately, which usually means driving its state machine andpassing the payload of the packet to the sink. Having done this, thedata press waits to be informed of the next piece of raw data to process.316OPTIMIZING EXECUTION TIMEThe text above assumes that everything works normally.
If only thatwere always the case! Unfortunately it’s not and so you also need tohandle the receiving of invalid or corrupt packets gracefully, as shown inFigure 8.6.Figure 8.6 Dynamics of the Data Press pattern (invalid packet)The exact error handling you need to perform depends on the domainto which you are applying this pattern. However, a common approachis to send a response back to the source that you identified a problem.This response might be a request to resend the data or it could be todisconnect the communication channel.
Occasionally you also needto involve the sink in handling the error, although this isn’t shown inFigure 8.6.ImplementationPacket EventsEvent Mixin (see page 93) is used to decouple the stencil and the datapress and provide increased flexibility so that you can accommodatemore easily any future changes. The interface described here is based ona warm event signal approach in which each of the methods are calledsimply to indicate that the stencil has identified a packet of a certain type.If the class implementing this interface wishes to obtain any further detailsthen they can be accessed through the stencil.
However, you might findDATA PRESS317the hot event signal approach more suitable whereby the information thatmight be of interested to the derived class is passed as parameters to eachmethod.15class MPacketEvents{public:// This class has a function such as this for each possible packet typevoid MpePacketTypeEvent(CStencil& aPacket);void MpeInvalidPacket(CStencil& aPacket, TInt aError);};Data PressThis class needs to be implemented as a C class as it owns the stencilobject. However, it would also be needed if this class contained datamembers used to store the state of your middleware component betweenindividual packets.Note that to be able to show the implementation of this pattern, we’vechosen to use a fictional C class to represent the raw data type. However,this is only an example, and existing Symbian OS classes or other classtypes could be used instead.class CDataPress : public CBase, public MPacketEvents{public:static CDataPress* NewL(CSource& aSource, CSink& aSink);∼CDataPress();void NewData(CRawData* aRawData);public: // Functions from MPacketEventsvoid MpePacketTypeEvent(CStencil& aPacket);void MpeInvalidPacket(CStencil& aPacket, TInt aError);private:CDataPress(CSource& aSource, CSink& aSink);void ConstructL();...private:CSource& iSource;CSink& iSink;CStencil* iStencil;// Further data members may be needed to track the state of the// component between packets...};The following function is used to create the CStencil object andensure it’s accessible at all times.
The other functions involved in the15 SeeChapter 4 for more details on the difference between warm and hot event signals.318OPTIMIZING EXECUTION TIMEtwo-phase construction of this class aren’t shown as they should be asper the standard approach.void CDataPress::ConstructL(){iStencil = CStencil::NewL();}The destructor of the class has to clean up the resources used bythe class as appropriate.
In particular it destroys the stencil class. Thisis shown to highlight the fact that the class is not deleted at any timebetween these two points and is re-used each time a new data packet ispassed to the data press.void CDataPress::∼CDataPress(){delete iStencil;// You may need to de-register from either the sink or the source here...}The function below is called by the source when it has processed theinbound data. This transfers ownership of the raw data to the data pressclass which itself immediately transfers ownership to the stencil.void CDataPress::NewData(CRawData* aRawData){iStencil->NewData(aRawData);}The MpePacketTypeEvent() method shown below is just oneof multiple similar functions called when the stencil has identified aparticular packet type.
This is where your domain-specific algorithmshould be implemented. After this you should pass on the payload ofthe packet to the sink. Just the payload is passed on as the header of thepacket is only relevant at the current layer. By calling NewData() in thisway, the ownership of the raw data is passed to the sink.void CDataPress::MpePacketTypeEvent(CStencil& aPacket){// Domain-specific implementation goes here. For instance, you might// need to change state as appropriate for the packet type....iSink->NewData(aPacket->TransferPayload());aPacket->Reset();}DATA PRESS319When an error occurs during the parsing of a packet, the followingmethod is called.
Exactly how you handle this is domain-specific but acommon response is shown below:void CDataPress::MpeInvalidPacket(CStencil& aPacket, TInt aError){iSource->SendResponse();aPacket.Reset();}StencilThis class models the raw data as a packet, which is mostly aboutproviding the data press with accessors to the fields of the packet header.To provide this information, it internally processes the raw data it is givento model depending on the packet type.
The stencil is a classic C classbecause it owns data on the heap – the CRawData.class CStencil : public CBase{public:static CStencil* NewL(MPacketEvents& aPacketObserver);∼CStencil();void NewData(CRawData* aRawData);CRawData* TransferPayload();// Accessors for the header fieldsTPacketType PacketType();THeaderField HeaderField(); // One per field in the headerprivate:CStencil(MPacketEvents& aPacketObserver);void ConstructL();void Reset();TBool PacketComplete();void ProcessL();void ValidatePacketTypeL(); // One per packet typevoid HandlePacketTypeL();// One per packet typeprivate:enum TPacketType{EPacketType,EAnotherPacketType,...};MPacketEvents& iPacketObserver;CRawData* iRawData;};320OPTIMIZING EXECUTION TIMEThe functions involved in the two-phase construction are as you mightexpect. However, the destructor needs to delete any raw data that ithappens to be holding onto at the time:void CStencil::∼CStencil(){Reset();}void CStencil::Reset(){delete iRawData;iRawData = NULL;}The real lifecycle of this class starts when it is provided with raw data toprocess:void CStencil::NewData(CRawData* aRawData){// Take ownership of the dataif (iRawData){// Assumes CRawData has pre-allocated space to link in the// additional data without the possibility of a failure, e.g.
by// having a TDblQue data memberiRawData.Append(aRawData);}else // We don’t already have any data{iRawData = aRawData;}// Process the data once we have collected a complete packetif (PacketComplete()){TRAPD(err, ProcessL());if (err != KErrNone){ // There’s been an error so notify the observeriPacketObserver->MpeInvalidPacket(*this, err);}}// else wait for more data}The above relies on the ProcessL() function to do the main work ofvalidating and then interpreting the raw data appropriately. Note how thisfunction uses Escalate Errors (see page 32) to simplify the error handling.The NewData() function acts as a high-level trap harness to catch anyerrors raised within this function so that they can be passed on to the datapress to be dealt with.However, all this function does directly is to ensure the correct parsingfunctions are called for each packet type.
This is implemented below as aDATA PRESS321simple switch statement but if performance is particularly critical thena lookup table can be more efficient, if less readable. You might find thatarranging the order of the switch statement so that the most frequentlyoccurring packet types come first provides a sufficient optimization thatdoesn’t sacrifice the legibility of your code.void CStencil::ProcessL(){// Ensure that this function is only called when we’ve got raw dataASSERT(iRawData);switch (PacketType());{case EPacketType:{HandlePacketTypeL();break;}case EAnotherPacketType:{// Call the method to handle this packet typebreak;}// Deal with all the other packet types heredefault:{User::Leave(KErrCorrupt);}}The handling of each packet type is also mostly domain-specific. It isin these functions that most of the work is done directly to process eachindividual packet type.
First of all, the packet is validated. If this succeeds,it is safe to continue, which allows you to more easily parse the raw databy relying on it complying with the expected format.This parsing might include transforming the raw data; if it can be donein place without the need for any additional RAM then all the better.However, if you can’t transform it in place then you should ensure that,during the construction of the stencil, you pre-allocate sufficient RAM toperform any allocations that do occur.
This is to prevent a system errorfrom unexpectedly halting the parsing of a packet. Note that if you dotransform the raw data you need to ensure that iRawData contains thefinal result as that is what is passed on to the sink.void CStencil::HandlePacketTypeL() // One per packet type{// Check the packet before doing anything elseValidatePacketTypeL();// Domain-specific handling322OPTIMIZING EXECUTION TIME// Notify observer that this packet type has been identifiediPacketObserver->MpePacketTypeEvent(*this);}The private functions to validate each packet type are mostly domainspecific however there are some common themes.void CStencil::ValidatePacketTypeL() // One per packet type{// You almost always need to check the packet lengthif (PacketLength() < KPacketTypeMinLength ||PacketLength() > KPacketTypeMaxLength){User::Leave(KErrCorrupt);}// You might also have a checksum field in the header that you can// match against the checksum on the whole packet that you’ve received}The following function is called by the data press to obtain the payloadof the packet, usually to pass on to the sink.