I'm a little confused by a document I have for the structure of a data packet I want to read via UDP. I have an app set up which is working at getting very basic messages in the form of a string so I know the UDP side works. Now I need to take it to the next level and receive the messages and even more so decipher this into usable information.
I have been sent a document on the structure of the packet but I feel it's over my head so I am just looking for pointers on how to handle this. I have attached a pdf of some of the examples of the message structure. Do i create a module with this structure defined? Do I then do some sort of mask to extract the various segments?
I'm basically trying to gauge if this is something I can learn to figure out or if I need to pay someone to just write the code for me but that feels not only expensive but also doesn't help me further my understanding.
Data comes in as packets, and packets are handled by Winsock. Winsock hands off the data portion to your program. The packets themselves are either streamed, or handled as a record by your program. A record may consist of several packets. UDP is connectionless, so if it is not normally used for streaming sensitive data. A more complete explanation can be seen here: https://www.vbforums.com/showthread....B6-Simple-Sock
Do i create a module with this structure defined? Do I then do some sort of mask to extract the various segments?
You just need a module with several "extracting primitives" -- i.e. functions which can read Long from a byte-array (in network byte order) or a fixed-length String or any other data type your documentation lists in the packet description.
For instance this buffer management helper module
Code:
'--- mdMyBuffer.bas
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (Ptr() As Any) As Long
Private Declare Function IsBadReadPtr Lib "kernel32" (ByVal lp As Long, ByVal ucb As Long) As Long
Public Type MyBuffer
Data() As Byte
Pos As Long
Size As Long
End Type
Public Sub BufferWriteString(uOutput As MyBuffer, sValue As String)
BufferWriteArray uOutput, StrConv(sValue, vbFromUnicode)
End Sub
Public Sub BufferWriteArray(uOutput As MyBuffer, baSrc() As Byte)
Dim lSize As Long
With uOutput
lSize = pvArraySize(baSrc)
If lSize > 0 Then
.Size = pvArrayWriteBlob(.Data, .Size, VarPtr(baSrc(0)), lSize)
End If
End With
End Sub
Public Sub BufferWriteLong(uOutput As MyBuffer, ByVal lValue As Long, Optional ByVal Size As Long = 1)
Static baTemp(0 To 3) As Byte
Dim lPos As Long
With uOutput
If Size <= 1 Then
BufferWriteBlob uOutput, VarPtr(lValue), Size
Else
lPos = .Size
BufferWriteBlob uOutput, 0, Size
Call CopyMemory(baTemp(0), lValue, 4)
.Data(lPos + 0) = baTemp(Size - 1)
.Data(lPos + 1) = baTemp(Size - 2)
If Size >= 3 Then .Data(lPos + 2) = baTemp(Size - 3)
If Size >= 4 Then .Data(lPos + 3) = baTemp(Size - 4)
End If
End With
End Sub
Public Sub BufferWriteBlob(uOutput As MyBuffer, ByVal lPtr As Long, ByVal lSize As Long)
uOutput.Size = pvArrayWriteBlob(uOutput.Data, uOutput.Size, lPtr, lSize)
End Sub
Private Function pvArrayWriteBlob(baBuffer() As Byte, ByVal lPos As Long, ByVal lPtr As Long, ByVal lSize As Long) As Long
Dim lBufPtr As Long
'--- peek long at ArrPtr(baBuffer)
Call CopyMemory(lBufPtr, ByVal ArrPtr(baBuffer), 4)
If lBufPtr = 0 Then
pvArrayAllocate baBuffer, Clamp(lPos + lSize, 256)
ElseIf UBound(baBuffer) < lPos + lSize - 1 Then
pvArrayReallocate baBuffer, lPos + lSize
End If
If lSize > 0 And lPtr <> 0 Then
Debug.Assert IsBadReadPtr(lPtr, lSize) = 0
Call CopyMemory(baBuffer(lPos), ByVal lPtr, lSize)
End If
pvArrayWriteBlob = lPos + lSize
End Function
Public Sub BufferReadLong(uInput As MyBuffer, lValue As Long, Optional ByVal Size As Long = 1)
Static baTemp(0 To 3) As Byte
lValue = 0
With uInput
If .Pos + Size <= pvArraySize(.Data) Then
If Size <= 1 Then
lValue = .Data(.Pos)
Else
baTemp(Size - 1) = .Data(.Pos + 0)
baTemp(Size - 2) = .Data(.Pos + 1)
If Size >= 3 Then baTemp(Size - 3) = .Data(.Pos + 2)
If Size >= 4 Then baTemp(Size - 4) = .Data(.Pos + 3)
Call CopyMemory(lValue, baTemp(0), Size)
End If
End If
.Pos = .Pos + Size
End With
End Sub
Public Sub BufferReadBlob(uInput As MyBuffer, ByVal lPtr As Long, ByVal lSize As Long)
Dim baDest() As Byte
BufferReadArray uInput, baDest, lSize
lSize = pvArraySize(baDest)
If lSize > 0 Then
Call CopyMemory(ByVal lPtr, baDest(0), lSize)
End If
End Sub
Public Sub BufferReadArray(uInput As MyBuffer, baDest() As Byte, ByVal lSize As Long)
With uInput
If lSize < 0 Then
lSize = pvArraySize(.Data) - .Pos
End If
If lSize > 0 Then
pvArrayAllocate baDest, lSize
If .Pos + lSize <= pvArraySize(.Data) Then
Call CopyMemory(baDest(0), .Data(.Pos), lSize)
ElseIf .Pos < pvArraySize(.Data) Then
Call CopyMemory(baDest(0), .Data(.Pos), pvArraySize(.Data) - .Pos)
End If
Else
Erase baDest
End If
.Pos = .Pos + lSize
End With
End Sub
Public Sub BufferReadString(uInput As MyBuffer, sValue As String, ByVal lSize As Long)
Dim baTemp() As Byte
BufferReadArray uInput, baTemp(), lSize
sValue = StrConv(baTemp, vbUnicode)
End Sub
Private Property Get pvArraySize(baArray() As Byte) As Long
Dim lPtr As Long
'--- peek long at ArrPtr(baArray)
Call CopyMemory(lPtr, ByVal ArrPtr(baArray), 4)
If lPtr <> 0 Then
pvArraySize = UBound(baArray) + 1
End If
End Property
Private Sub pvArrayAllocate(baRetVal() As Byte, ByVal lSize As Long)
If lSize > 0 Then
ReDim baRetVal(0 To lSize - 1) As Byte
Else
baRetVal = vbNullString
End If
End Sub
Private Sub pvArrayReallocate(baArray() As Byte, ByVal lSize As Long)
If lSize > 0 Then
ReDim Preserve baArray(0 To lSize - 1) As Byte
Else
baArray = vbNullString
End If
End Sub
Private Function Clamp( _
ByVal lValue As Long, _
Optional ByVal lMin As Long = -2147483647, _
Optional ByVal lMax As Long = 2147483647) As Long
Select Case lValue
Case lMin To lMax
Clamp = lValue
Case Is < lMin
Clamp = lMin
Case Is > lMax
Clamp = lMax
End Select
End Function
. . . can be used like this
Code:
Option Explicit
Private Sub Form_Load()
Dim uBuffer As MyBuffer
'--- this comes from network
Dim baRecv(0 To 99) As Byte
baRecv(0) = 12
baRecv(1) = 13
baRecv(2) = 14
BufferWriteArray uBuffer, baRecv
Dim lMessageID As Long
Dim lVersion As Long
Dim sSenderID As String
Dim lTimestamp As Long
Dim lDataLength As Long
Dim lCrc16 As Long
Dim baData() As Byte
BufferReadLong uBuffer, lMessageID, Size:=4
BufferReadLong uBuffer, lVersion, Size:=2
BufferReadString uBuffer, sSenderID, 10
BufferReadLong uBuffer, lTimestamp, Size:=4
BufferReadLong uBuffer, lDataLength, Size:=4
BufferReadLong uBuffer, lCrc16, Size:=2
BufferReadArray uBuffer, baData, lDataLength
'--- reset buffer and parse PDO data
uBuffer.Pos = 0
uBuffer.Size = 0
BufferWriteArray uBuffer, baData
Dim lPacketType As Long
Dim lAlarmIndex As Long
Dim lAlarmStatus As Long
Dim lEnableIndex As Long
Dim lEnableStatus As Long
Do While uBuffer.Pos < uBuffer.Size
BufferReadLong uBuffer, lPacketType, Size:=4
Select Case lPacketType
Case &HF600
BufferReadLong uBuffer, lAlarmIndex, Size:=4
BufferReadLong uBuffer, lAlarmStatus, Size:=4
'--- ToDo: call alarm handler
Case &HF700
BufferReadLong uBuffer, lEnableIndex, Size:=4
BufferReadLong uBuffer, lEnableStatus, Size:=4
'--- ToDo: call enable handler
Case &HFF00
'--- ToDo: call end-of-data handler
Case Else
Err.Raise vbObjectError, , "Unknown PDO type (&H" & Hex$(lPacketType) & ")"
End Select
Loop
End Sub
Thanks both of you! I read and "think" I understood the post you posted couttsj. I then went on to use your code wqweto.
I have found that I also have a few integer types - can I get away with using the BufferReadLong for this or should I be using a BufferReadInteger? Also just before the Case iPacketType there is a do while ubuffer.pos<ubuffer size however both those have been changed to equal 0 a few lines earlier? Am I misunderstanding something there?
Variable Size is important. Longs are 4 byte reads, integers are 2 byte reads.
If you gobble up to many bytes with one read you will lose your alignment and start grabbing data from other fields.
I use one of the two following classes for these tasks:
The best way to handle unknown data is approach it as byte data. That way you are not dependant on how the operating system might interpret the raw data.
Thanks both of you! I read and "think" I understood the post you posted couttsj. I then went on to use your code wqweto.
I have found that I also have a few integer types - can I get away with using the BufferReadLong for this or should I be using a BufferReadInteger? Also just before the Case iPacketType there is a do while ubuffer.pos<ubuffer size however both those have been changed to equal 0 a few lines earlier? Am I misunderstanding something there?
Cheers
Using Integer for practically *anything* in VB6 is suboptimal. You can of course come up with BufferReadInteger but keep in mind that this will be very inconvenient to handle *unsigned* 16-bit values (which is usually the case with network protocols) but keeping unsigned 16-bit values in Longs is the way to go.
Keep in mind that currently BufferReadLong as implemented expects integral values to be persisted in network byte order (so called big endian encoding) while VB6 and x86 architecture in particular use little endianness. Your protocol documentation must explicitly state how multi-byte integral values are sent or if this is missing then you should figure it out on your own with trials and errors.
The buffer struct consists of a byte-array, a size which keeps how much of this byte-array is actually full with data (rest of the byte-array up to UBound is still free space) and a position which keeps currently reached offset for reading.
After resetting the buffer with .Pos = 0 and .Size = 0 there is a BufferWriteArray call which places just parsed "PDO data" in the buffer which increments its available size. In the loops reading elements from the buffer increments its current position until reaching final buffer size.