NTSTATUS
StreamOobCopyDataToFlatBuffer(
   _Inout_ STREAM_EDITOR* streamEditor,
   _Inout_ NET_BUFFER_LIST* netBufferListChain,
   size_t totalDataLength,
   DWORD streamFlags
   )
/* ++

   This function copies the data described by NBL(s) into a flat buffer. 
   
   It reuses the FwpsCopyStreamDataToBuffer API (via StreamCopyDataForInspection)
   by creating a FWPS_STREAM_DATA struct.

-- */
{
   NTSTATUS status = STATUS_SUCCESS;

   FWPS_STREAM_DATA streamData = {0};

   if (totalDataLength > 0)
   {
      streamData.netBufferListChain = netBufferListChain;
      streamData.dataLength =  totalDataLength;
      streamData.flags = streamFlags;

      streamData.dataOffset.netBufferList = netBufferListChain;
      streamData.dataOffset.netBuffer = 
         NET_BUFFER_LIST_FIRST_NB(streamData.dataOffset.netBufferList);
      streamData.dataOffset.mdl = 
         NET_BUFFER_CURRENT_MDL(streamData.dataOffset.netBuffer);
      streamData.dataOffset.mdlOffset = 
         NET_BUFFER_CURRENT_MDL_OFFSET(streamData.dataOffset.netBuffer);

      if (StreamCopyDataForInspection(
            streamEditor,
            &streamData
            ) == FALSE)
      {
         status = STATUS_NO_MEMORY;
      }
   }

   return status;
}
void
StreamInlineEdit(
   _Inout_ STREAM_EDITOR* streamEditor,
   _In_ const FWPS_INCOMING_VALUES* inFixedValues,
   _In_ const FWPS_INCOMING_METADATA_VALUES* inMetaValues,
   _In_ const FWPS_FILTER* filter,
   _In_ const FWPS_STREAM_DATA* streamData,
   _Inout_ FWPS_STREAM_CALLOUT_IO_PACKET* ioPacket,
   _Inout_ FWPS_CLASSIFY_OUT* classifyOut
   )
/* ++

   This function implements the state machine that scans the content 
   and computes the number of bytes to permit, bytes to block, and 
   performs stream injection to replace the blocked data.

-- */
{
   UINT findLength = (UINT) strlen(configStringToFind);
   UINT replaceLength = (UINT) strlen(configStringToReplace);

   if ((streamData->flags & FWPS_STREAM_FLAG_SEND_DISCONNECT) || 
       (streamData->flags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT))
   {
      if (streamEditor->dataLength > 0)
      {
         StreamEditFlushData(
            streamEditor,
            inMetaValues->flowHandle,
            filter->action.calloutId,
            inFixedValues->layerId,
            streamData->flags
            );

         streamEditor->dataLength = 0;
         streamEditor->dataOffset = 0;
      }

      NT_ASSERT(streamEditor->inlineEditState == INLINE_EDIT_WAITING_FOR_DATA);

      ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
      classifyOut->actionType = FWP_ACTION_PERMIT;

      if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
      {
         classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
      }

      goto Exit;
   }

   if (streamData->dataLength == 0)
   {
      ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
      classifyOut->actionType = FWP_ACTION_PERMIT;

      if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
      {
         classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
      }

      goto Exit;
   }

   if (streamEditor->inlineEditState != INLINE_EDIT_SKIPPING)
   {
      if ((streamData->dataLength < findLength) && 
          !(classifyOut->flags & FWPS_CLASSIFY_OUT_FLAG_NO_MORE_DATA))
      {
         ioPacket->streamAction = FWPS_STREAM_ACTION_NEED_MORE_DATA;
         ioPacket->countBytesRequired = findLength;

         classifyOut->actionType = FWP_ACTION_NONE;
         goto Exit;
      }
   }

   switch (streamEditor->inlineEditState)
   {
      case INLINE_EDIT_WAITING_FOR_DATA:
      {
         if (StreamCopyDataForInspection(
               streamEditor,
               streamData
               ) == FALSE)
         {
            ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
            classifyOut->actionType = FWP_ACTION_NONE;
            goto Exit;
         }

         //
         // Pass-thru to scanning
         //
      }
      case INLINE_EDIT_SCANNING:
      {
         UINT i;
         BYTE* dataStart =  (BYTE*)streamEditor->scratchBuffer + streamEditor->dataOffset;
         BOOLEAN found = FALSE;

         for (i = 0; i < streamEditor->dataLength; ++i)
         {
            if (i + findLength <= streamEditor->dataLength)
            {
               if (RtlCompareMemory(
                     dataStart + i,
                     configStringToFind,
                     findLength
                     ) == findLength)
               {
                  found = TRUE;

                  streamEditor->inlineEditState = INLINE_EDIT_MODIFYING;

                  if (i != 0)
                  {
                     ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
                     ioPacket->countBytesEnforced = i;

                     classifyOut->actionType = FWP_ACTION_PERMIT;

                     if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
                     {
                        classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
                     }

                     streamEditor->dataOffset += i;
                     streamEditor->dataLength -= i;

                     break;
                  }
                  else
                  {
                     goto modify_data;
                  }
               }
            }
            else
            {
               if (classifyOut->flags & FWPS_CLASSIFY_OUT_FLAG_NO_MORE_DATA)
               {
                  break;
               }

               if (RtlCompareMemory(
                     dataStart + i,
                     configStringToFind,
                     streamEditor->dataLength - i
                     ) == streamEditor->dataLength - i)
               {
                  found = TRUE;  // this is a partial find
   
                  ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
                  ioPacket->countBytesEnforced = i;

                  classifyOut->actionType = FWP_ACTION_PERMIT;

                  if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
                  {
                     classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
                  }

                  RtlMoveMemory(
                     streamEditor->scratchBuffer,
                     dataStart + i,
                     streamEditor->dataLength - i
                     );

                  streamEditor->dataOffset = 0;
                  streamEditor->dataLength = streamEditor->dataLength - i;

                  streamEditor->inlineEditState = INLINE_EDIT_SKIPPING;
   
                  break;
               }
            }
         }

         if (!found)
         {
            ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
            ioPacket->countBytesEnforced = 0;

            classifyOut->actionType = FWP_ACTION_PERMIT;

            if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
            {
               classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
            }

            streamEditor->dataOffset = 0;
            streamEditor->dataLength = 0;

            streamEditor->inlineEditState = INLINE_EDIT_WAITING_FOR_DATA;
         }

         break;
      }
      case INLINE_EDIT_SKIPPING:
      {
         ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
         ioPacket->countBytesEnforced = 0;

         classifyOut->actionType = FWP_ACTION_BLOCK;
         classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;

         streamEditor->inlineEditState = INLINE_EDIT_WAITING_FOR_DATA;

         break;
      }
      case INLINE_EDIT_MODIFYING:

modify_data:
      
      {
         NTSTATUS status;
         NET_BUFFER_LIST* netBufferList;

         status = FwpsAllocateNetBufferAndNetBufferList(
                     gNetBufferListPool,
                     0,
                     0,
                     gStringToReplaceMdl,
                     0,
                     replaceLength,
                     &netBufferList
                     );

         if (!NT_SUCCESS(status))
         {
            ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
            classifyOut->actionType = FWP_ACTION_NONE;
            goto Exit;
         }

         status = FwpsStreamInjectAsync(
                     gInjectionHandle,
                     NULL,
                     0,
                     inMetaValues->flowHandle,
                     filter->action.calloutId,
                     inFixedValues->layerId,
                     streamData->flags,
                     netBufferList,
                     replaceLength,
                     StreamInjectCompletionFn,
                     NULL
                     );

         if (!NT_SUCCESS(status))
         {
            FwpsFreeNetBufferList(netBufferList);

            ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
            classifyOut->actionType = FWP_ACTION_NONE;
            goto Exit;
         }

         ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
         ioPacket->countBytesEnforced = findLength;

         classifyOut->actionType = FWP_ACTION_BLOCK;
         classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;

         streamEditor->dataOffset += findLength;
         streamEditor->dataLength -= findLength;

         if (streamEditor->dataLength > 0)
         {
            streamEditor->inlineEditState = INLINE_EDIT_SCANNING;
         }
         else
         {
            streamEditor->dataOffset = 0;

            streamEditor->inlineEditState = INLINE_EDIT_WAITING_FOR_DATA;
         }

         break;
      }
      default:
         NT_ASSERT(FALSE);
         break;
   };

Exit:

   return;
}