Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 61
Текст из файла (страница 61)
This function transfers theownership of the payload data to the caller although the raw data formingthe header is still owned by the stencil.CRawData* CStencil::TransferPayload(){TUint headerLength = HeaderLength();// Get raw data, point to the payloadCRawData* ret = iRawData->Mid(headerLength);// Reduce amount of data owned to just the headeriRawData->SetLength(headerLength);return ret;}The only functions that we have left to implement are the basicheader field accessors such as PacketType(). These functions use theCRawData interface to extract the value required on demand.
They haveto be aware of issues such as managing the endianess of the data andlack of contiguous memory.ConsequencesPositives• You reduce the error paths that have to be dealt with during theparsing by treating the stencil object as an Immortal (see page 53)DATA PRESS323and allocating it upfront rather than allocating it just in time to handleeach packet received.• Latency is reduced as a consequence of using Immortal (see page 53)as the lifetime for the stencil, since this reduces the CPU cycles neededto parse each packet.• RAM usage is reduced by sharing data and not fragmenting the heapwith lots of small RAM allocations for the stencil object.• Power usage is reduced as a natural consequence of the reduced RAMand CPU used.• Maintenance costs are reduced.
By encapsulating the processing ofraw data into the stencil class, any changes to the format of thepacket can be easily dealt with either by changing the implementation of the existing stencil class or by providing an alternativestencil type with the same overall interface but a different implementation.
Maintenance costs are also kept down by having a clearseparation between interpreting the raw data and the decisions basedon it.Negatives• The stencil relies on the persistence of the raw data all the whileit is processing it. This must be enforced outside the stencil classand potentially outside of your middleware component. For instance,it may be provided by convention between protocols in a dataprocessing stack. Similarly, the stencil class must be able to be surethat whilst it ‘owns’ the raw data it is working on, no-one else will tryto manipulate the data at the same time.• By pre-allocating the stencil, you reduce the overall RAM available in the system even if the stencil is not currently being usedto model a packet.
If the stencil just models the packet headerthis probably isn’t significant. However, if RAM is required to perform transformations on the raw data it might be more of a problem.• This pattern does not address directly how you store several instancesof a message and keep state information about them. If you do needto do this then you’ll need to extend this pattern by upgrading thedata press class to store a collection of stencil classes.
You’ll probablyfind that the stencil classes are re-usable so long as you let them keepownership of their raw data and pass a copy of it on the sink. If youneed to store a lot of stencil objects then you should consider usingFlyweight [Gamma et al., 1994].324OPTIMIZING EXECUTION TIMEExample ResolvedThe Symbian OS Bluetooth component AVDTP uses this pattern toimplement the signaling channel described in the associated protocolspecification. In this case, the data press is a class called CSignallingChannel which owns a single stencil, or CAvdtpInboundSignallingMessage, class. Rather than show you the entire classand duplicate much of the implementation shown above, we focus onthe domain-specific functions that deal with the parsing, validation andhandling of a selection of the AVDTP packet types.The first example is based on the group of packets which deal with adevice requesting a remote device to list the stream end points (SEPs) thatit supports and hence what transport services it provides.
These packetshave the following specific types:• command – when received from a remote device, this is a request tosend a response detailing the locally supported SEPs• response accept – received from a remote device in response from adiscover command sent by this device and lists the SEPs the remotedevice supports• response reject – as for response accept except that the initial requesthas been rejected for some reason (this is not shown below as itdoesn’t add much to the understanding of this pattern).The data press object provides the following packet event functions forthese packets:class CSignallingChannel : public CBase, ... // Plus some other classes{public:void DiscoverIndication(CAvdtpInboundSignallingMessage& aMessage);void DiscoverConfirm(CAvdtpInboundSignallingMessage& aMessage);void InvalidPacket(CAvdtpInboundSignallingMessage &aMessage,TInt aError);...};Discover CommandThe parsing for the discover command packet type is implemented asfollows.void CAvdtpInboundSignallingMessage::HandleDiscoverCommandL(){// Simple validationCheckPacketLengthL(KAvdtpDiscoverCommandMinimumLength,DATA PRESS325KAvdtpDiscoverCommandMaximumLength);// Simple handlingiSignallingChannel.DiscoverIndication(*this);}The following code shows how the data press handles this particularpacket event without going into all the details.
Note that aMessage isn’tused here because this packet type has no payload.void CSignallingChannel::DiscoverIndication(CAvdtpInboundSignallingMessage& aMessage){// Create transaction ready for the responseCSignallingTransaction* transaction = PrepareSignallingResponse(aMessage->Id(), EResponseAccept, EAvdtpDiscover);if (transaction){// Ask the session to contribute to the packet on behalf of// the clientiClientSession->DiscoverIndication(*transaction);// We now deem the packet constructed and ready to send to the remote// deviceSendResponse(transaction);}aMessage.Reset(); // Clean up any remaining raw data}Discover Response AcceptThe response accept packet provides a more complex parsing example.This is because the payload of the packet lists the SEPs the remote devicesupports and hence there is more data that needs to be validated.void CAvdtpInboundSignallingMessage::HandleDiscoverResponseAcceptL(){// More complex validationCheckPacketLengthL(KAvdtpDiscoverAcceptMinimumLength,KAvdtpDiscoverAcceptMaximumLength);// The remainder of the packet should be formed of a list of// information about the supported SEPsif (Payload().Length() % KAvdtpPacketSepInfoSize != 0){User::Leave(KErrCorrupt);}// The packet must contain information about least one SEPif (Payload().Length() == 0){326OPTIMIZING EXECUTION TIMEUser::Leave(KErrNotFound);}// Parsing completeiSignallingChannel.DiscoverConfirm(*this);}When the data press receives the discover confirm packet event, itpasses it on up to the local client with the information about the remotedevice and, in this case, there is a payload that needs to be passed to theclient.
However, rather than just passing the data straight to the client,the data press uses the stencil to parse the list of SEPs so that it canstore the information about what transaction services the remote devicesupports to help maintain the protocol’s state.void CSignallingChannel::DiscoverConfirm(CAvdtpInboundSignallingMessage& aMessage){CSignallingTransaction* transaction = FindTransaction(aMessage->Id());if (transaction){TAvdtpSepList seps;aMessage->DiscoveredSeps(seps); // Used instead of TransferPayload()StoreDiscoveredSeps(seps);transaction->Client()->DiscoverConfirm(seps);delete transaction;}aMessage.Reset(); // Clean up any remaining raw data}The DiscoveredSeps() function above is a good example of anaccessor function even though, strictly speaking, it isn’t accessing theheader information.
Rather than passing the SEP list back as a return fromthe function, it takes a SEP list as an out parameter since this is moreefficient for an object on the stack. Note that a data copy is neededhere as the SEP list is going to be needed after the packet providing theinformation has been used up.void CAvdtpInboundSignallingMessage::DiscoveredSeps(TAvdtpSepList& aSeps){// Parse the data out of the packetaSeps.iNumSeps = (Payload().Length()) / KAvdtpPacketSepInfoSize;for (TInt i=0; i<aSeps.iNumSeps; i++){// Extract the info about each SEP// Note the use of accessor functions in the following codeTAvdtpSepInfo sepInfo;sepInfo.SetSeid(SepSeid(i));DATA PRESS327sepInfo.SetInUse(SepInUse(i));...// Store the informationaSeps.iSepsList.Append(sepInfo);}}The function above relies on the following accessor functions thatinterpret the raw data directly.
Note that in each of them a small amountof data needs to be copied. This is because the underlying raw data isstored in non-contiguous memory which can’t be easily parsed. Since thesize of the data is small, a copy is made to a contiguous memory locationbefore the information in the required header field is extracted.TSeid CAvdtpInboundSignallingMessage::SepSeid(TInt aSepOffset){TUint byte;iRawData.CopyOut(byte, (aSepOffset * KAvdtpPacketSepInfoSize), 1);TSeid seid = TSeid((byte >> KAvdtpPacketSeidOffset) &KAvdtpPacketSeidMask);return seid;}TBool CAvdtpInboundSignallingMessage::SepInUse(TInt aSepOffset){TUint byte;iRawData.CopyOut(byte, (aSepOffset * KAvdtpPacketSepInfoSize), 1);return (byte & KAvdtpPacketSepInUseMask ? ETrue : EFalse);}Error HandlingWhen a parse error occurs, the protocol handles commands and responsesdifferently since they differ in who is expecting a response:• Commands have been received from a remote device and hence needto be sent an appropriate rejection response.