#include "precomp.h" // // UP.CPP // Update Packager // // Copyright(c) Microsoft 1997- // #define MLZ_FILE_ZONE ZONE_NET // // UP_FlowControl() // Checks if we've switched between slow and fast throughput // void ASHost::UP_FlowControl(UINT newBufferSize) { DebugEntry(ASHost::UP_FlowControl); if (newBufferSize > (LARGE_ORDER_PACKET_SIZE / 2)) { if (m_upfUseSmallPackets) { m_upfUseSmallPackets = FALSE; TRACE_OUT(("UP_FlowControl: FAST; use large packets")); } } else { if (!m_upfUseSmallPackets) { m_upfUseSmallPackets = TRUE; TRACE_OUT(("UP_FlowControl: SLOW; use small packets")); } } DebugExitVOID(ASHost::UP_FlowControl); } // // UP_Periodic() // // Called periodically, to send graphical updates as orders and/or screen // data. // void ASHost::UP_Periodic(UINT currentTime) { BOOL fSendSD = FALSE; BOOL fSendOrders = FALSE; UINT tmpTime; UINT timeSinceOrders; UINT timeSinceSD; UINT timeSinceTrying; DebugEntry(ASHost::UP_Periodic); // // This is a // performance critical part of the scheduling so we apply some // heuristics to try and keep the overheads down. // // 1.If there was no back pressure last time then we check the // rate of accumulation of screendata over the last period. // If it was high then we apply a time slice to the sending // of screendata. // // 2.If the rate of order accumulation was also high then we // apply a timeslice to the order accumulation as well, just // to avoid too high a CPU overhead trying to send orders // when we will eventually fail to keep up. We keep this // time period low because the objective is simply to avoid // sending hundreds of packets containing few orders each. // (On the other hand, we want to send the single textout // following a keystoke ASAP so we must not timeslice all the // time.) // // 3.If neither orders nor screendata is piling up quickly then // we do a full send immediately. // // 4.If there was back pressure on the last send then we still // send orders, but always on the time slice, independent of // the order accumulation rate. // // Note that we cannot sample the accumulation rates for every // pass because the app doing the drawing may be interrupted by // us for a few hundred milliseconds. Therefore we only sample // the bounds every VOLUME_SAMPLE milliseconds. // // timeSinceSD = currentTime - m_upLastSDTime; timeSinceOrders = currentTime - m_upLastOrdersTime; timeSinceTrying = currentTime - m_upLastTrialTime; // // Sample the accumulation rates. // m_upSDAccum += BA_QueryAccumulation(); m_upOrdersAccum += OA_QueryOrderAccum(); // // Sample the throughput over the last period to see whether we // can operate in rapid respose mode or whether we should // timeslice. // if (timeSinceTrying > DCS_VOLUME_SAMPLE) { // // Take the newly accumulated deltas. // m_upDeltaSD = m_upSDAccum; m_upDeltaOrders = m_upOrdersAccum; // // Store time of last retrieval. // m_upLastTrialTime = currentTime; // // Reset the running totals. // m_upSDAccum = 0; m_upOrdersAccum = 0; } // // If we are way out of line then send updates. Not that this // will reset the update timer independent of whether the send // works or not, so that we don't enter this arm continually // when we time out but are in a back pressure situation // // The long stop timer is there to catch apps that keep a // continual flow of orders/SD at above the suppression rate. // We want to tune our heuristics to avoid this, but if it // happens than we must send the data eventually. The problem // is that this objective clashes with the scenario of the user // paging down twenty times, where our most efficient approach // is to let him run and snapshot the SD at the end, rather // than every PERIOD_LONG milliseconds. (A screen snapshot // will stop the host for a second!). // if (timeSinceSD > DCS_SD_UPDATE_LONG_PERIOD) { fSendSD = TRUE; } else { // // We only disregard our time slicing if the rate of orders // and screendata is low enough to warrant it. If the rate // is too high then hold off so that we can do some packet // consolidation. If we had no back pressure last time or // the screendata rate is now low enough then try sending // SD as well as orders. // // The order threshold is measured in number of orders over // the period. Screendata is measured in the total area // accumulated (prior to any spoiling). // if (!m_upBackPressure) { if (m_upDeltaOrders < DCS_ORDERS_TURNOFF_FREQUENCY) { fSendOrders = TRUE; if (m_upDeltaSD < DCS_BOUNDS_TURNOFF_RATE) { if ((timeSinceSD < DCS_SD_UPDATE_SHORT_PERIOD) && (m_upDeltaSD > DCS_BOUNDS_IMMEDIATE_RATE)) { fSendSD = FALSE; } else { fSendSD = TRUE; } } } } // // Even in a back pressure situation we try and send orders // periodically to keep current. If we overflow the order // buffer then we will constrain the buffer size to prevent // sending too many non-productive orders. (But we dont // turn orders off because we still want the user to see // things happening.) Generally we send orders immediately, // provided the rate of accumulation is within the limits. // This test is to time slice orders if they are being // generated at a high rate. The constant must be // reasonably small otherwise we force the order buffer to // overflow and order processing will be turned off. // if (!fSendSD && !fSendOrders) { if (timeSinceOrders > DCS_ORDER_UPDATE_PERIOD) { fSendOrders = TRUE; } } } // // Now we can go ahead and try sending! First look to see if // we can do both screendata and orders // if (fSendSD) { // // Indicate no back pressure (even if this send is // triggered by a timout our initial assumption is no back // pressure). Back pressure will be reinstated by // SendUpdates if necessary. // m_upBackPressure = FALSE; UPSendUpdates(); // // Sending screendata can take a long time. It messes up // our heuristics unless we adjust for it. // tmpTime = GetTickCount(); timeSinceTrying -= (tmpTime - currentTime); m_pShare->m_dcsLastScheduleTime = tmpTime; m_upLastSDTime = tmpTime; m_upLastOrdersTime = tmpTime; } else { if (fSendOrders) { // // Either the update rate is too high or we are // experiencing back pressure so just send the orders // and not the screendata. This is because we want to // avoid entering screendata mode as a result of order // back pressure for as long as we can. The screendata // will come later, when things have settled down a bit // m_upLastOrdersTime = currentTime; m_upBackPressure = TRUE; if (!UPSendUpdates()) { // // This is the only real action so leave all the // tracing separate for cleanliness. If there are // orders in transit then everything is fine. If none // are sent for a while then we want to break out of // our SD back pressure wait. This is because we are // only sampling the flow rates every DCS_VOLUME_SAMPLE msecs, // but we dont want to have to wait that long to flush the SD. // We cannot increase the flow sample rate because then // it becomes too erratic because of system scheduling. // m_upBackPressure = FALSE; UPSendUpdates(); m_upLastSDTime = currentTime; } } } DebugExitVOID(ASHost::UP_Periodic); } // // UPSendUpdates() // Actually tries to allocate and send orders + screen data. What it does // depends on // * Presence of back-pressure due to previous send failures // * How much screen data & orders there are // * Whether we're in serious spoiling mode and can't keep up // * What packet size to send // // Returns: // # of packets sent // UINT ASHost::UPSendUpdates(void) { BOOL synced; BOOL ordersSent; UINT numPackets = 0; DebugEntry(ASHost::UPSendUpdates); // // If we actually have updates to send then try to send a sync token. // if ((OA_GetTotalOrderListBytes() > 0) || (m_sdgcLossy != 0) || (m_baNumRects > 0)) { synced = UP_MaybeSendSyncToken(); // // Only send updates if we have sent the sync token succesfully. // if (synced) { // // There is no outstanding sync token waiting to be sent, so we // can send the orders and screen data updates. // // // Send accumulated orders. If this call fails (probably out // of memory) then don't send any other updates - we'll try // sending the whole lot later. The orders MUST be sent before // the screen data. // if (PM_MaybeSendPalettePacket()) { ordersSent = UPSendOrders(&numPackets); if (!ordersSent) { m_upBackPressure = TRUE; } else { // // Orders sent OK so go for the screendata, provided // the caller wants us to. // if (!m_upBackPressure) { // // We may now try and send screen data. However, // we need to be careful not to do this too // frequently, because DC-Share is now being // scheduled to send as soon as network buffers // become available. On the other hand, some // apps respond to keystrokes with screendata so // we cannot just slow it down! // // The approach is to have SendScreenDataArea // return the amount of data sent, together with // an indication as to whether we hit back pressure // // We return these to dcsapi which has control of // when we are scheduled and passes the paramaters // in again // // TRACE_OUT(( "Sending SD")); SDG_SendScreenDataArea(&m_upBackPressure, &numPackets); } else { // // We sent the orders OK an so we must reset // the back pressure indicator even though we // were asked not to send screendata // TRACE_OUT(( "Orders sent and BP relieved")); m_upBackPressure = FALSE; } } } } } else { m_upBackPressure = FALSE; } DebugExitDWORD(ASHost::UPSendUpdates, numPackets); return(numPackets); } // // UP_MaybeSendSyncToken() // BOOL ASHost::UP_MaybeSendSyncToken(void) { PUPSPACKET pUPSPacket; #ifdef _DEBUG UINT sentSize; #endif // _DEBUG DebugEntry(ASHost::UP_MaybeSendSyncToken); // // Check to see if we should send a sync token. // if (m_upfSyncTokenRequired) { // // The sync packet consists of an updates packets as far as the end // of the header. // pUPSPacket = (PUPSPACKET)m_pShare->SC_AllocPkt(PROT_STR_UPDATES, g_s20BroadcastID, sizeof(UPSPACKET)); if (!pUPSPacket) { // // We will try again later. // TRACE_OUT(("Failed to alloc UP sync packet")); } else { // // Fill in the packet contents. // pUPSPacket->header.header.data.dataType = DT_UP; pUPSPacket->header.updateType = UPD_SYNC; // // Now send the packet to the remote application. // if (m_pShare->m_scfViewSelf) m_pShare->UP_ReceivedPacket(m_pShare->m_pasLocal, &(pUPSPacket->header.header)); #ifdef _DEBUG sentSize = #endif // _DEBUG m_pShare->DCS_CompressAndSendPacket(PROT_STR_UPDATES, g_s20BroadcastID, &(pUPSPacket->header.header), sizeof(*pUPSPacket)); TRACE_OUT(("UP SYNC packet size: %08d, sent %08d", sizeof(*pUPSPacket), sentSize)); // // The sync packet was successfully sent. // m_upfSyncTokenRequired = FALSE; } } DebugExitBOOL(ASHost::UP_MaybeSendSyncToken, (!m_upfSyncTokenRequired)); return(!m_upfSyncTokenRequired); } // // UPSendOrders(..) // // Sends all accumulated orders. // // Returns: // TRUE if all orders successfully sent // FALSE if send failed (e.g. if unable to allocate network packet) // // BOOL ASHost::UPSendOrders(UINT * pcPackets) { PORDPACKET pPacket = NULL; UINT cbOrderBytes; UINT cbOrderBytesRemaining; UINT cbPacketSize; BOOL rc = TRUE; #ifdef _DEBUG UINT sentSize; #endif // _DEBUG DebugEntry(ASHost::UPSendOrders); // // Find out how many bytes of orders there are in the Order List. // cbOrderBytesRemaining = UPFetchOrdersIntoBuffer(NULL, NULL, NULL); // // Process any orders on the list. // if (cbOrderBytesRemaining > 0) { TRACE_OUT(( "%u order bytes to fetch", cbOrderBytesRemaining)); // // Keep sending packets while there are some orders to do. // while (cbOrderBytesRemaining > 0) { UINT cbMax; // // Make sure the order size does not exceed the max packet // size. // cbMax = (m_upfUseSmallPackets) ? SMALL_ORDER_PACKET_SIZE : LARGE_ORDER_PACKET_SIZE; cbPacketSize = min(cbOrderBytesRemaining, (cbMax - sizeof(ORDPACKET) + 1)); // // Allocate a packet to send the data in. // pPacket = (PORDPACKET)m_pShare->SC_AllocPkt(PROT_STR_UPDATES, g_s20BroadcastID, sizeof(ORDPACKET) + cbPacketSize - 1); if (!pPacket) { // // Failed to allocate a packet. We skip out immediately - // we'll try again later. // TRACE_OUT(("Failed to alloc UP order packet, size %u", sizeof(ORDPACKET) + cbPacketSize - 1)); rc = FALSE; DC_QUIT; } // // Transfer as many orders into the packet as will fit. // cbOrderBytes = cbPacketSize; cbOrderBytesRemaining = UPFetchOrdersIntoBuffer( pPacket->data, &pPacket->cOrders, &cbOrderBytes); TRACE_OUT(( "%u bytes fetched into %u byte pkt. %u remain.", cbOrderBytes, cbPacketSize, cbOrderBytesRemaining)); // // If no order bytes were transferred then try again with a // Large Order Packet. // if (cbOrderBytes == 0) { // // We need to use a larger packet to transfer the // orders into. (The first order must be a very large // order such as a large bitmap cache update). // S20_FreeDataPkt(&(pPacket->header.header)); // // cbOrderBytesRemaining may not accurate if there are // any MemBlt orders in the order heap. This is // because we may have to insert a color table order // and / or a bitmap bits order before the MemBlt. // // To avoid getting into an infinite loop if there is // only a MemBlt remaining but we actually have to send // a color table and / or a bitmap bits order // (cbOrderBytesRemaining would never get set high // enough to allow us to send the color table / bitmap // bits order), make the buffer at least large enough // to hold the largest amount of data required for all // the parts of a MemBlt. // // // The maximum number of bytes required to send a MemBlt order. This is // The size of the largest possible color table order // + the size of the largest possible bitmap bits order // + the size of the largest MemBlt order. // cbPacketSize = sizeof(BMC_COLOR_TABLE_ORDER) + (256 * sizeof(TSHR_RGBQUAD)) + sizeof(BMC_BITMAP_BITS_ORDER_R2) + sizeof(MEM3BLT_R2_ORDER) + MP_CACHE_CELLSIZE(MP_LARGE_TILE_WIDTH, MP_LARGE_TILE_HEIGHT, m_usrSendingBPP); cbPacketSize = max(cbPacketSize, cbOrderBytesRemaining); if (cbPacketSize > (UINT)(LARGE_ORDER_PACKET_SIZE - sizeof(ORDPACKET) + 1)) { TRACE_OUT(("Too many order bytes for large packet(%d)", cbOrderBytesRemaining)); cbPacketSize = LARGE_ORDER_PACKET_SIZE - sizeof(ORDPACKET) + 1; } pPacket = (PORDPACKET)m_pShare->SC_AllocPkt(PROT_STR_UPDATES, g_s20BroadcastID, sizeof(ORDPACKET) + cbPacketSize - 1); if (!pPacket) { TRACE_OUT(("Failed to alloc UP order packet, size %u", sizeof(ORDPACKET) + cbPacketSize - 1)); rc = FALSE; DC_QUIT; } // // Transfer as many orders into the packet as will // fit. // cbOrderBytes = cbPacketSize; cbOrderBytesRemaining = UPFetchOrdersIntoBuffer( pPacket->data, &pPacket->cOrders, &cbOrderBytes ); // // If no orders were transferred then something has // gone wrong. Probably flow control kicked in or // a dekstop switch occurred. // Return failure now! // Hopefully things will sort themselves out later // or we will resort to sending updates as screen // data once the order accumulation heap becomes // full. // if (cbOrderBytes == 0) { WARNING_OUT(("No orders fetched into %u byte packet, %u bytes left", cbPacketSize, cbOrderBytesRemaining)); S20_FreeDataPkt(&(pPacket->header.header)); rc = FALSE; DC_QUIT; } } // // Fill in the packet header. // pPacket->header.header.data.dataType = DT_UP; pPacket->header.updateType = UPD_ORDERS; pPacket->sendBPP = (TSHR_UINT16)m_usrSendingBPP; // // If encoding is switched on, update the data size to reflect // it with encoded orders // if (m_pShare->m_oefOE2EncodingOn) { pPacket->header.header.dataLength = sizeof(ORDPACKET) + cbOrderBytes - 1 - sizeof(S20DATAPACKET) + sizeof(DATAPACKETHEADER); } // // Now send it. // if (m_pShare->m_scfViewSelf) m_pShare->UP_ReceivedPacket(m_pShare->m_pasLocal, &(pPacket->header.header)); #ifdef _DEBUG sentSize = #endif // _DEBUG m_pShare->DCS_CompressAndSendPacket(PROT_STR_UPDATES, g_s20BroadcastID, &(pPacket->header.header), sizeof(ORDPACKET) + cbOrderBytes - 1); TRACE_OUT(("UP ORDERS packet size: %08d, sent %08d", sizeof(ORDPACKET) + cbOrderBytes - 1, sentSize)); ++(*pcPackets); } } DC_EXIT_POINT: DebugExitBOOL(ASHost::UPSendOrders, rc); return(rc); } // // // UPFetchOrdersIntoBuffer(..) // // Encodes orders from the Order List and copies them into the supplied // buffer, then frees up the memory of each order copied. // // Orders are copied until the buffer is full or there are no more orders. // // Returns: // The number of order bytes that were NOT returned. // i.e. 0 if all orders were returned. // A simple way to find out the total number of order bytes // in the Order List is to call the function with a buffer length // of zero. // // *pcbBufferSize is updated to contain the total number of bytes // returned. // // UINT ASHost::UPFetchOrdersIntoBuffer ( LPBYTE pBuffer, LPTSHR_UINT16 pcOrders, LPUINT pcbBufferSize ) { LPINT_ORDER pListOrder; LPINT_ORDER pCurrentOrder; UINT cbFreeBytesInBuffer; UINT cOrdersCopied; LPBYTE pDst; UINT cbOrderSize; UINT ulRemainingOrderBytes; BOOL processingMemBlt; DebugEntry(ASHost::UPFetchOrdersIntoBuffer); // // Make a quick exit if the Order List length is being queried. // if ( (pcbBufferSize == NULL) || (*pcbBufferSize == 0) ) { goto fetch_orders_exit; } // // Initialize the buffer pointer and size. // pDst = pBuffer; cbFreeBytesInBuffer = *pcbBufferSize; // // Keep a count of the number of orders we copy. // cOrdersCopied = 0; // // Return as many orders as possible. // pListOrder = OA_GetFirstListOrder(); TRACE_OUT(( "First order: 0x%08x", pListOrder)); while (pListOrder != NULL) { if (pListOrder->OrderHeader.Common.fOrderFlags & OF_INTERNAL) { // // This is an internal order. Currently SBC is the only // component to use internal orders, so get SBC to process it. // SBC_ProcessInternalOrder(pListOrder); // // Internal order must not get sent over the wire, so skip on // to the next order // pListOrder = OA_RemoveListOrder(pListOrder); continue; } if (ORDER_IS_MEMBLT(pListOrder) || ORDER_IS_MEM3BLT(pListOrder)) { // // This is a MEMBLT or a MEM3BLT so we have to do some extra // processing... This function returns us a pointer to the // next order which should be sent - this will often not be the // MEMBLT, but a color table order or a bitmap bits order. // if (!SBC_ProcessMemBltOrder(pListOrder, &pCurrentOrder)) { // // This can fail if // * we're low on memory // * we changed from 8BPP to 24BPP sending, because // somebody left the share, and we have queued up // SBC orders that we can no longer process. // TRACE_OUT(("Failed to process SBC order, fall back to SDG")); pListOrder = OA_RemoveListOrder(pListOrder); continue; } processingMemBlt = TRUE; } else { // // This isn't a MEMBLT or a MEM3BLT - just set pCurrentOrder to // be pListOrder // pCurrentOrder = pListOrder; processingMemBlt = FALSE; } if (m_pShare->m_oefOE2EncodingOn) { // // Encoding is switched on. // Encode the order into the next free space in the buffer // cbOrderSize = OE2_EncodeOrder( pCurrentOrder, pDst, (TSHR_UINT16)cbFreeBytesInBuffer ); TRACE_OUT(( "Encoded size, %u bytes", cbOrderSize)); } else { // // Copy the order into the buffer. // cbOrderSize = COM_ORDER_SIZE( ((LPCOM_ORDER)(&(pCurrentOrder->OrderHeader.Common)))); if (cbOrderSize <= cbFreeBytesInBuffer) { memcpy(pDst, (LPCOM_ORDER)(&(pCurrentOrder->OrderHeader.Common)), cbOrderSize); } else { // // No room for this order in this packet. // cbOrderSize = 0; } } // // Check whether the order was copied into the buffer. // if (cbOrderSize == 0) { // // The order was too big to fit in this buffer. // Exit the loop - this order will go in the next packet. // break; } // // Update the buffer pointer past the encoded order. // pDst += cbOrderSize; cbFreeBytesInBuffer -= cbOrderSize; cOrdersCopied++; if (processingMemBlt) { // // If we are processing a MEMBLT order, we have to notify SBC // that we've dealt with it successfully so that it returns us // a different order next time. // SBC_OrderSentNotification(pCurrentOrder); } if (pCurrentOrder == pListOrder) { // // We successfully copied the order into the buffer - on to the // next one UNLESS we haven't processed the last one we picked // out of the order list i.e. pCurrentOrder is not the same as // pListOrder. This will happen if we just processed a color // table order or a bitmap bits order returned from // SBC_ProcessMemBltOrder (if we processed the MEMBLT itself, // we can safely move on to the next order). // pListOrder = OA_RemoveListOrder(pListOrder); } } // // Fill in the packet header. // if (pcOrders != NULL) { *pcOrders = (TSHR_UINT16)cOrdersCopied; } // // Update the buffer size to indicate how much data we have // written. // *pcbBufferSize -= cbFreeBytesInBuffer; TRACE_OUT(( "Returned %d orders in %d bytes", cOrdersCopied, *pcbBufferSize)); fetch_orders_exit: // // Return the number of bytes still to be processed // ulRemainingOrderBytes = OA_GetTotalOrderListBytes(); DebugExitDWORD(ASHost::UPFetchOrdersIntoBuffer, ulRemainingOrderBytes); return(ulRemainingOrderBytes); } // // UP_ReceivePacket() // void ASShare::UP_ReceivedPacket ( ASPerson * pasPerson, PS20DATAPACKET pPacket ) { PUPPACKETHEADER pUPPacket; DebugEntry(ASShare::UP_ReceivedPacket); ValidatePerson(pasPerson); if (!pasPerson->m_pView) { // // Updates for parties which we don't recognise as hosts are just // discarded. // // NOTE: // 2.0 Win95 does not have HET, where we kick off sharing/unsharing. // But it did have TT, and the packet type/messages were defined // cleverly for HET so that 2.0 Win95 works the same. When they // start to share, we get a PT_TT packet with a non-zero count. // The difference really is that the number is apps for Win95 2.0 // and HWNDs for everybody else. // WARNING_OUT(("UP_ReceivedUpdates: Ignoring updates from person [%d] not hosting", pasPerson->mcsID)); DC_QUIT; } pUPPacket = (PUPPACKETHEADER)pPacket; switch (pUPPacket->updateType) { case UPD_SCREEN_DATA: SDP_ReceivedPacket(pasPerson, pPacket); break; case UPD_ORDERS: OD_ReceivedPacket(pasPerson, pPacket); break; case UPD_PALETTE: PM_ReceivedPacket(pasPerson, pPacket); break; case UPD_SYNC: // // We need to reset our INCOMING decoding info since the sender // resets his OUTGOING encoding info for a sync. // OD2_SyncIncoming(pasPerson); // // NOTE: // We do not need to reset INCOMING data for // PM -- the host won't send us old palette references // RBC -- the host won't send us old bitmap references. // Even though it would be nice to delete the existing // bitmaps, recreating the cache is a hassle. // CM -- the host won't send us old cursor references // SSI -- the host won't send us old savebits references // break; default: ERROR_OUT(("Unknown UP packet type %u from [%d]", pUPPacket->updateType, pasPerson->mcsID)); break; } DC_EXIT_POINT: DebugExitVOID(ASShare::UP_ReceivedPacket); }