Decoding H264 Stream Always Returns MF_E_TRANSFORM_NEED_MORE_INPUT - h.264

I'm attempting to decode raw h264 from a network stream using the Media Foundation Transform CLSID_MSH264DecoderMFT. Setting up the transform seems to work and it's accepting data. However, no matter how much data I provide, it always returns MF_E_TRANSFORM_NEED_MORE_INPUT.
The document says, that the decoder will skip over all data until it finds valid Sequence and Picture Parameters. I'm providing this and then a raw data frame along with start codes:
1 00 00 00 01 67 42 c0 28 da 01 e0 19 fe 7c 05 a8 08 08 0a 00 00 03 00 02 00 00 03 00 61 1e 30 65
2 40 00 00 00 01 68 ce 3c 80 00 00 00 01 00 00 0e 6c 41 9a e0 eb 08 84 3c 14 ff fe 10 ff f8 64 14
3 f0 88 20 11 55 d5 7e 19 11 17 17 c5 c5 3f 05 00 a3 86 41 08 8a ae ab 58 8c 1f 11 88 cd f8 9f ff
4 f8 9d 78 21 f9 2a bf e2 3e 04 1f f8 20 08 92 7c 0e 33 52 67 e1 48 74 32 f8 5c 5f ca fd 77 12 df
5 3a 0f 93 11 89 2f 26 98 76 16 65 9b 78 87 77 ff ff fe 27 c6 fe b1 39 34 27 04 17 55 f0 61 fe 23
Above is only a partial sample, but it's representative of the data I provide to the transform.
Transform Setup:
ComPtr<IUnknown> pUnknown = nullptr;
HRESULT hResult = CoCreateInstance(CLSID_MSH264DecoderMFT, nullptr, CLSCTX_INPROC_SERVER, IID_IUnknown, &pUnknown);
if (S_OK != hResult) {
LogError("Failed to create H264 decoder");
return false;
}
hResult = pUnknown->QueryInterface(IID_PPV_ARGS(&mVideoDecoder));
if (hResult != S_OK) {
LogError("Failed to create H264 decoder");
return false;
}
ComPtr<IMFMediaType> pInputMediaType = nullptr;
hResult = MFCreateMediaType(&pInputMediaType);
if (S_OK != hResult) {
return false;
}
pInputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
pInputMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
std::shared_ptr<VideoMp4Track> videoTrack = mDemuxer->getVideoTrack();
uint32_t width = videoTrack->getWidth();
uint32_t height = videoTrack->getHeight();
MFSetAttributeSize(pInputMediaType.Get(), MF_MT_FRAME_SIZE, width, height);
MFSetAttributeRatio(pInputMediaType.Get(), MF_MT_PIXEL_ASPECT_RATIO, width, height);
MFSetAttributeRatio(pInputMediaType.Get(), MF_MT_FRAME_RATE, videoTrack->getFrameRate(), 1);
pInputMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive);
ComPtr<IMFAttributes> attributes;
mVideoDecoder->GetAttributes(&attributes);
hResult = attributes->SetUINT32(CODECAPI_AVLowLatencyMode, 1);
if (hResult != S_OK) {
LogError("Failed to set low latency mode. Video might be choppy.");
}
hResult = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, 1);
if (hResult != S_OK) {
LogError("Failed to set GPU acceleration. Video might be choppy.");
}
hResult = mVideoDecoder->SetInputType(0, pInputMediaType.Get(), 0);
if (hResult != S_OK) {
LogError("Failed to set input type for decoder");
return false;
}
ComPtr<IMFMediaType> pOutputType = nullptr;
hResult = MFCreateMediaType(&pOutputType);
if (S_OK != hResult) {
return false;
}
pOutputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
pOutputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
MFSetAttributeSize(pOutputType.Get(), MF_MT_FRAME_SIZE, width, height);
MFSetAttributeRatio(pOutputType.Get(), MF_MT_PIXEL_ASPECT_RATIO, width, height);
MFSetAttributeRatio(pOutputType.Get(), MF_MT_FRAME_RATE, videoTrack->getFrameRate(), 1);
hResult = mVideoDecoder->SetOutputType(0, pOutputType.Get(), 0);
if (hResult != S_OK) {
LogError("Failed to set input type for decoder");
return false;
}
// Notify the resampler.
hResult = mVideoDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, NULL);
if (S_OK != hResult) {
LogError("Failed to send flush command to the decoder.");
return false;
}
hResult = mVideoDecoder->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL);
if (S_OK != hResult) {
LogError("Failed to send notify command to the decoder.");
return false;
}
hResult = mVideoDecoder->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL);
if (S_OK != hResult) {
LogError("Failed to send notify command to the decoder.");
return false;
}
I have no idea why it isn't able to decode, would appreciate any help.
Thanks.
Edit:
DataPtr transformData = MakeDataPtr();
uint32_t startCode = 0x01000000;
std::shared_ptr<VideoMp4Track> video = mImpl->mDemuxer->getVideoTrack();
transformData->appendBytes(&startCode, 4);
DataPtr sps = video->getSequenceParameters();
transformData->appendData(*sps);
transformData->appendBytes(&startCode, 4);
DataPtr pps = video->getPictureParameters();
transformData->appendData(*pps);
transformData->appendBytes(&startCode, 4);
transformData->appendData(*sampleData);
transformData->appendBytes(&startCode, 4);
ComPtr<IMFSample> pSample = mImpl->createMFSample(transformData->getBytes(), transformData->getSize());
if (nullptr == pSample) {
LogError("Failed to create the buffer for decoder input");
return nullptr;
}
HRESULT hResult = mImpl->mVideoDecoder->ProcessInput(0, pSample.Get(), 0);
if (hResult != S_OK) {
if (hResult == MF_E_NOTACCEPTING) {
mImpl->mVideoDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, NULL);
hResult = mImpl->mVideoDecoder->ProcessInput(0, pSample.Get(), 0);
}
else {
LogError("Error feeding to resampler...");
return nullptr;
}
}
DWORD dwStatus = 0;
// outputDataBuffer is empty, need to create it.
MFT_OUTPUT_DATA_BUFFER outputDataBuffer{};
ComPtr<IMFSample> pVideoSample = nullptr;
hResult = MFCreateSample(&pVideoSample);
if (S_OK != hResult) {
LogError("Failed to create a media sample for decoder output");
return false;
}
ComPtr<IMFMediaBuffer> pOutputBuffer = nullptr;
hResult = MFCreateMemoryBuffer(sampleData->getSize(), &pOutputBuffer);
if (S_OK != hResult) {
LogError("Failed to create a memory buffer for decoder output");
return false;
}
pVideoSample->AddBuffer(pOutputBuffer.Get());
outputDataBuffer.pSample = pVideoSample.Get();
do {
hResult = mImpl->mVideoDecoder->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus);
if (hResult == MF_E_TRANSFORM_NEED_MORE_INPUT) {
// conversion end
break;
}
I've omitted the rest because it never gets further, it just stays in this loop populating the transform.
Edit 2:
(Not) Working sample on github
https://github.com/pma07pg/h264
The sample code was too large to dump here so I've put the main.cpp on github. Should be able to just put it into a VS project and run it off the bat.

There are few bugs in your code.
1.) You didn't account for the start code size
yours:
const uint32_t parameterInputSize = sizeof(pictureParameters) + sizeof(sequenceParameters);
mine:
const uint32_t parameterInputSize = sizeof(startCode) + sizeof(pictureParameters) + sizeof(startCode) + sizeof(sequenceParameters);
Your 'mdat's contain more than one AccessUnit. Each AccessUnit is prefixed with its length which you have to replace with a start code.
Your 'mdat':
'mdat' = <size> data[0] | <size> data[1] | ... | <size> data[n] |
Replace the size with a start code and break the multiple Access Units into individual Access Units.
Required decoder input:
00 00 00 01 data[0]
00 00 00 01 data[1]
...
00 00 00 01 data[n]
See details here: https://github.com/go4shoe/MedieFoundationExample

Related

Node MySQL connection resultset having issue

Connected Node using MySQL (using mysql2/promise)
on
const sql = `
SELECT *
FROM Applicant
WHERE ApplicationId = ?
`;
const result = await this.mysqlManager.Query(sql,number);
console.log(result);
public async Query(sql: any, parameters?: any, connection?: PoolConnection): Promise<any>
{
const con = connection || (await this.GetConnection());
try
{
const data = await con.query(sql, parameters);
return data;
}
catch (error)
{
con.release();
throw error;
}
}
but got response
ColumnDefinition {
_buf: <Buffer 3a 00 00 16 03 64 65 66 08 70 72 6f 70 65 6c 6c 64 09 41 70 70 6c 69 63 61 6e 74 09 41 70 70 6c 69 63 61 6e 74 05 50 43
69 74 79 05 50 43 69 74 79 0c ... 2376 more bytes>,
_clientEncoding: 'utf8',
_catalogLength: 3,
_catalogStart: 2329,
_schemaLength: 8,
_schemaStart: 2333,
_tableLength: 9,
_tableStart: 2342,
_orgTableLength: 9,
_orgTableStart: 2352,
_orgNameLength: 16,
_orgNameStart: 2379,
characterSet: 224,
encoding: 'utf8',
name: 'SalaryDayRangeTo',
columnLength: 80,
columnType: 253,
flags: 0,
decimals: 0
}
Can someone please help me with this issue?
const data = await con.query(sql, parameters); return data[0];
Since first element consist of data with other attributes consisting schema information.

Find used CRC-16 algorithm

I'm struggling to reverse engineer a section of data associated with a CRC-16 checksum.
I know the polynom used to calculate the original checksums is 0x8408 but nothing else, I don't know initial value (if any), final XOR value (if any), if the input or the result is reflected...
It seems like there is a known CRC-16 generator using thing polynom, CRC-16-CCITT but despite everything I've tried I just can't understand how the original checksum is being calculated.
Here is the data I've got with their respective checksums. I also included a byte that is between the data and the checksum, it's incremental and I'm not whether it's calculated or not. (see the last two lines, data is almost the same, increment is not the same and yet the checksum are identical)
| DATA |Inc|CRC|
|----------------------------------------------------------|---|---|
00 00 00 00 00 00 01 ef f7 fe ef ff fd ef fb fa fd a2 aa 21 01 f4 e0
00 00 00 00 00 00 01 ef f7 fd ef ff fd fe fb fa fd a2 aa 21 02 f4 d1
00 00 00 00 00 00 01 f7 fe fd fd ff fd df ff fb fd a2 aa 21 03 f4 cd
00 00 00 00 00 00 01 f7 fe fe fd ff f7 ef ff fa fd a2 aa 21 04 f4 c2
00 00 00 00 00 00 01 ef f7 fe ef ff fe ef fb fa fd a2 aa 21 05 f4 db
00 00 00 00 00 00 01 ef f7 fe ef ff fd ef fb fa fd a2 aa 21 06 f4 db
The last byte of each line appears to be 0xF3 + the negative sum of all but the last byte (including the 0xF4). This code works for the 5 examples:
typedef unsigned char uint8_t;
static uint8_t data0[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xef,0xf7,0xfe,0xef,
0xff,0xfd,0xef,0xfb,0xfa,0xfd,0xa2,0xaa,0x21,0x01,0xf4,0xe0};
static uint8_t data1[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xef,0xf7,0xfd,0xef,
0xff,0xfd,0xfe,0xfb,0xfa,0xfd,0xa2,0xaa,0x21,0x02,0xf4,0xd1};
static uint8_t data2[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xf7,0xfe,0xfd,0xfd,
0xff,0xfd,0xdf,0xff,0xfb,0xfd,0xa2,0xaa,0x21,0x03,0xf4,0xcd};
static uint8_t data3[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xf7,0xfe,0xfe,0xfd,
0xff,0xf7,0xef,0xff,0xfa,0xfd,0xa2,0xaa,0x21,0x04,0xf4,0xc2};
static uint8_t data4[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xef,0xf7,0xfe,0xef,0xff,
0xfe,0xef,0xfb,0xfa,0xfd,0xa2,0xaa,0x21,0x05,0xf4,0xdb};
static uint8_t data5[] =
{0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xef,0xf7,0xfe,0xef,0xff,
0xfd,0xef,0xfb,0xfa,0xfd,0xa2,0xaa,0x21,0x06,0xf4,0xdb};
int main()
{
size_t i;
uint8_t c = 0xf3;
uint8_t s;
s = c;
for(i = 0; i < sizeof(data0)-1; i++)
s -= data0[i];
if (data0[i] != s)
printf("mismatch\n");
s = c;
for (i = 0; i < sizeof(data1) - 1; i++)
s -= data1[i];
if (data1[i] != s)
printf("mismatch\n");
s = c;
for (i = 0; i < sizeof(data2) - 1; i++)
s -= data2[i];
if (data2[i] != s)
printf("mismatch\n");
s = c;
for (i = 0; i < sizeof(data3) - 1; i++)
s -= data3[i];
if (data3[i] != s)
printf("mismatch\n");
s = c;
for (i = 0; i < sizeof(data2) - 1; i++)
s -= data4[i];
if (data4[i] != s)
printf("mismatch\n");
return 0;
}

How can I implement server side SMTP STARTTLS?

I am trying to implement a simple SMTP server using Vala and GLib + GIO.
Plain text communication is no problem so far, but when it comes to TLS using STARTTLS things get harder.
This is the code I have so far:
const string appname = "vsmtpd";
const string hostname = "myserver";
const uint16 listenport = 10025;
const string keyfile = "vsmtpd.key";
const string certfile = "vsmtpd.crt";
// TODO: Parse EHLO instead of constant string
const string username = "myclient";
void process_request_plain (InputStream input, OutputStream output) throws Error {
output.write (#"220 $hostname ESMTP $appname\n".data);
var data_in = new DataInputStream (input);
string line;
while ((line = data_in.read_line (null)) != null) {
stdout.printf ("%s\n", line);
line = line.chomp ();
if (line.substring (0, 5) == "EHLO ") {
output.write (#"250-$hostname Hello $username\n".data);
output.write ("250 STARTTLS\n".data);
}
else if (line == "STARTTLS") {
output.write ("220 Go ahead\n".data);
break;
}
else {
output.write ("502 Command not implemented\n".data);
}
}
}
int main () {
try {
TlsCertificate cert = new TlsCertificate.from_files
(certfile, keyfile);
var service = new SocketService ();
service.add_inet_port (listenport, null);
service.start ();
while (true) {
SocketConnection conn = service.accept (null);
process_request_plain (conn.input_stream, conn.output_stream);
TlsServerConnection tlsconn = TlsServerConnection.#new (conn, cert);
assert_nonnull (tlsconn);
// TODO: Is this neccessary?
tlsconn.accept_certificate.connect ((peer_cert, errors) => {
stdout.printf ("TLS accepting peer cert\n");
return true;
});
try {
tlsconn.handshake ();
stdout.printf ("TLS handshake ok\n");
} catch (Error e) {
stdout.printf ("TLS handshake failed\n");
stderr.printf ("%s\n", e.message);
}
}
} catch (Error e) {
stderr.printf ("%s\n", e.message);
}
return 0;
}
Given a valid SSL certificate in vsmtpd.key and vsmtpd.crt (which I generated with openssl req -x509 -newkey rsa:2048 -keyout vsmtpd.key -out vsmtpd.pem -days 365 -nodes) I start the program and I also run this OpenSSL command to test STARTTLS:
openssl s_client -connect localhost:10025 -starttls smtp -debug
The output from my program is:
EHLO openssl.client.net
STARTTLS
TLS handshake failed
Stream is already closed
The output from OpenSSL is:
CONNECTED(00000003)
read from 0x6ae470 [0x6af050] (4096 bytes => 26 (0x1A))
0000 - 32 32 30 20 6d 79 73 65-72 76 65 72 20 45 53 4d 220 myserver ESM
0010 - 54 50 20 76 73 6d 74 70-64 0a TP vsmtpd.
write to 0x6ae470 [0x6b0060] (25 bytes => 25 (0x19))
0000 - 45 48 4c 4f 20 6f 70 65-6e 73 73 6c 2e 63 6c 69 EHLO openssl.cli
0010 - 65 6e 74 2e 6e 65 74 0d-0a ent.net..
read from 0x6ae470 [0x6af050] (4096 bytes => 28 (0x1C))
0000 - 32 35 30 2d 6d 79 73 65-72 76 65 72 20 48 65 6c 250-myserver Hel
0010 - 6c 6f 20 6d 79 63 6c 69-65 6e 74 0a lo myclient.
read from 0x6ae470 [0x6af050] (4096 bytes => 13 (0xD))
0000 - 32 35 30 20 53 54 41 52-54 54 4c 53 0a 250 STARTTLS.
write to 0x6ae470 [0x7ffdb4aea9e0] (10 bytes => 10 (0xA))
0000 - 53 54 41 52 54 54 4c 53-0d 0a STARTTLS..
read from 0x6ae470 [0x6a13a0] (8192 bytes => 13 (0xD))
0000 - 32 32 30 20 47 6f 20 61-68 65 61 64 0a 220 Go ahead.
write to 0x6ae470 [0x6aefa0] (204 bytes => 204 (0xCC))
0000 - 16 03 01 00 c7 01 00 00-c3 03 03 0e ac 05 35 45 ..............5E
0010 - db 95 f6 a7 37 55 d8 ca-14 d7 5f 8e 6a 62 08 50 ....7U...._.jb.P
0020 - c9 81 b7 55 75 a8 4c 17-c0 a1 53 00 00 76 00 a5 ...Uu.L...S..v..
0030 - 00 a3 00 a1 00 9f 00 6b-00 6a 00 69 00 68 00 39 .......k.j.i.h.9
0040 - 00 38 00 37 00 36 00 88-00 87 00 86 00 85 00 9d .8.7.6..........
0050 - 00 3d 00 35 00 84 00 a4-00 a2 00 a0 00 9e 00 67 .=.5...........g
0060 - 00 40 00 3f 00 3e 00 33-00 32 00 31 00 30 00 9a .#.?.>.3.2.1.0..
0070 - 00 99 00 98 00 97 00 45-00 44 00 43 00 42 00 9c .......E.D.C.B..
0080 - 00 3c 00 2f 00 96 00 41-00 07 00 05 00 04 00 16 .<./...A........
0090 - 00 13 00 10 00 0d 00 0a-00 15 00 12 00 0f 00 0c ................
00a0 - 00 09 00 ff 02 01 00 00-23 00 23 00 00 00 0d 00 ........#.#.....
00b0 - 16 00 14 06 01 06 02 05-01 05 02 04 01 04 02 03 ................
00c0 - 01 03 02 02 01 02 02 00-0f 00 01 01 ............
read from 0x6ae470 [0x6b4500] (7 bytes => -1 (0xFFFFFFFFFFFFFFFF))
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 80 bytes and written 239 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
---
What I understand from the output my program closes the connection before the TLS handshake can complete. (I also tried using Thunderbird and Claws Mail)
What am I doing wrong here?
PS: I couldn't find any example on how to use GTLsServerConnection in a STARTTLS situation.
Update:
I tried -ssl2, -ssl3, -tls1, -tls1_1, -tls1_2 options of OpenSSL which also don't work.
openssl s_client -connect localhost:10025 -starttls smtp -state
yields:
CONNECTED(00000003)
SSL_connect:before/connect initialization
SSL_connect:SSLv2/v3 write client hello A
SSL_connect:error in SSLv2/v3 read server hello A
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 100 bytes and written 239 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
---
So the client sends "client hello A", but the server doesn't send a correct "server hello A".
As an alternative you can also try gnutls-cli --crlf --starttls-proto=smtp --port 10025 localhost.
The output from GNUTLS_DEBUG_LEVEL=11 ./vsmtpd is:
gnutls[2]: Enabled GnuTLS logging...
gnutls[2]: Intel SSSE3 was detected
gnutls[2]: Intel AES accelerator was detected
gnutls[2]: Intel GCM accelerator was detected
gnutls[2]: Enabled GnuTLS logging...
gnutls[2]: Intel SSSE3 was detected
gnutls[2]: Intel AES accelerator was detected
gnutls[2]: Intel GCM accelerator was detected
gnutls[3]: ASSERT: x509_b64.c:299
gnutls[9]: Could not find '-----BEGIN RSA PRIVATE KEY'
gnutls[3]: ASSERT: x509_b64.c:299
gnutls[9]: Could not find '-----BEGIN DSA PRIVATE KEY'
gnutls[3]: ASSERT: x509_b64.c:299
gnutls[9]: Could not find '-----BEGIN EC PRIVATE KEY'
gnutls[3]: ASSERT: privkey.c:503
gnutls[2]: Falling back to PKCS #8 key decoding
EHLO openssl.client.net
STARTTLS
gnutls[5]: REC[0xfa67e0]: Allocating epoch #0
gnutls[3]: ASSERT: gnutls_constate.c:586
gnutls[5]: REC[0xfa67e0]: Allocating epoch #1
gnutls[3]: ASSERT: gnutls_buffers.c:1138
gnutls[10]: READ: -1 returned from 0xfa4120, errno=0 gerrno=5
gnutls[3]: ASSERT: gnutls_buffers.c:364
gnutls[3]: ASSERT: gnutls_buffers.c:572
gnutls[3]: ASSERT: gnutls_record.c:1058
gnutls[3]: ASSERT: gnutls_record.c:1179
gnutls[3]: ASSERT: gnutls_buffers.c:1392
gnutls[3]: ASSERT: gnutls_handshake.c:1428
gnutls[3]: ASSERT: gnutls_handshake.c:3098
gnutls[3]: ASSERT: gnutls_db.c:334
TLS handshake failed
Stream is already closed
gnutls[5]: REC[0xfa67e0]: Start of epoch cleanup
gnutls[5]: REC[0xfa67e0]: End of epoch cleanup
gnutls[5]: REC[0xfa67e0]: Epoch #0 freed
gnutls[5]: REC[0xfa67e0]: Epoch #1 freed
The problem is hidden somewhere in the implementation of DataInputStream.
Once I removed it and used the following replacement for read_line () instead, it works just fine.
string? read_line (InputStream input) throws Error {
var buffer = new uint8[1];
var sb = new StringBuilder ();
buffer[0] = '\0';
while (buffer[0] != '\n') {
input.read (buffer);
sb.append_c ((char) buffer[0]);
}
return (string) sb.data;
}
void process_request_plain (InputStream input, OutputStream output) throws Error {
output.write (#"220 $hostname ESMTP $appname\n".data);
string line;
while ((line = read_line (input)) != null) {
stdout.printf ("%s\n", line);
line = line.chomp ();
if (line.substring (0, 5) == "EHLO ") {
output.write (#"250-$hostname Hello $username\n".data);
output.write ("250 STARTTLS\n".data);
}
else if (line == "STARTTLS") {
output.write ("220 Go ahead\n".data);
break;
}
else {
output.write ("502 Command not implemented\n".data);
}
}
}

Need help reverse engineering a CRC16

I'm trying to connect to the Safecom TA-810 (badge/registration system) to automate the process of calculating how long employee's have worked each day. Currently this is done by:
Pulling the data into the official application
Printing a list of all 'registrations'
Manually entering the values from the printed lists into our HR application
This is a job that can take multiple hours which we'd like to see automated. So far the official tech support has been disappointing and refused to share any details.
Using wireshark I have been capturing the UDP transmissions and have pretty much succeeded in understanding how the protocol is built up. I'm only having issues with what i suppose is a CRC field. I don't know how it is calculated (CRC type and parameters) and using which fields ...
This is how a message header looks like:
D0 07 71 BC BE 3B 00 00
D0 07 - Message type
71 BC - This i believe is the CRC
BE 3B - Some kind of session identifier. Stays the same for every message after the initial message (initial message has '00 00' as value)
00 00 - Message number. '01 00', '02 00', '03 00'
Some examples:
Header only examples
E8 03 17 FC 00 00 00 00 -> initial request (#0, no session nr)
D0 07 71 BC BE 3B 00 00 -> Initial response (#0, device sends a session nr)
4C 04 EF BF BE 3B 06 00 -> Message #6, still using the same session # as the initial response
Larger example, which has data
0B 00 07 E1 BE 3B 01 00 7E 45 78 74 65 6E 64 46 6D 74
I've also been trying to figure this out by reading the disassembled code from the original application. The screenshot below happens before the socket.sendto and seems to be related.
Any help will be extremely appreciated.
EDIT: Made some success with debugging the application using ollydbg. The CRC appears in register (reversed) EDX at the selected line in the following screenshot.
Take a look at CRC RevEng. If you can correctly identify the data that the CRC is operating on and the location of the CRC, you should be able to determine the CRC parameters. If it is a CRC.
I've managed to create a php script that does the CRC calculation by debugging the application using OllyDbg.
The CRC is calculated by Adding up every 2 bytes (every short). if the result is larger than a short, the 'most significant short' is added to the 'least significant short' until the result fits in a short. Finally, the CRC (short) is inverted.
I'll add my php script for completeness:
<?php
function CompareHash($telegram)
{
$telegram = str_replace(" ", "", $telegram);
$telegram_crc = substr($telegram, 4, 4);
$telegram = str_replace($telegram_crc, "0000", $telegram);
echo "Telegram: ", $telegram, ', Crc: ', $telegram_crc, ' (', hexdec($telegram_crc), ')<br />';
$crc = 0;
$i = 0;
while ($i < strlen($telegram))
{
$short = substr($telegram, $i, 4);
if (strlen($short) < 4) $short = $short . '00';
$crc += hexdec($short);
$i += 4;
}
echo "Crc: ", $crc, ', inverse: ', ~$crc;
// Region "truncate CRC to Int16"
while($crc > hexdec('FFFF'))
{
$short = $crc & hexdec ('FFFF');
// Region "unsigned shift right by 16 bits"
$crc = $crc >> 16;
$crc = $crc & hexdec ('FFFF');
// End region
$crc = $short + $crc;
}
// End region
// Region "invert Int16"
$crc = ~$crc;
$crc = $crc & hexdec ('FFFF');
// End region
echo ', shifted ', $crc;
if (hexdec($telegram_crc) == $crc)
{
echo "<br />MATCH!!! <br />";
}
else
{
echo "<br />failed .... <br />";
}
}
$s1_full = "E8 03 17 FC 00 00 00 00";
$s2_full = "D0 07 71 BC BE 3B 00 00";
$s3_full = "D0 07 4E D4 E1 23 00 00";
$s4_full = "D0 07 35 32 BE 3B 07 00 7E 44 65 76 69 63 65 4E 61 6D 65 3D 54 41 38 31 30 00";
$s5_full = "0B 00 39 6C BE 3B 05 00 7E 52 46 43 61 72 64 4F 6E";
CompareHash($s1_full);
CompareHash($s2_full);
CompareHash($s3_full);
CompareHash($s4_full);
CompareHash($s5_full);
?>
Thanks for the feedback!

Converting between SDP's sprop-parameter-sets and mkv's CodecPrivate

Is there some easy way to convert between h264 settings as stored in Matroska file:
+ CodecPrivate, length 36 (h.264 profile: Baseline #L2.0) hexdump
01 42 c0 14 ff e1 00 15 67 42 c0 14 da 05 07 e8
40 00 00 03 00 40 00 00 0c 03 c5 0a a8 01 00 04
68 ce 0f c8
and the same settings when streaming that matroska file using RTSP?:
a=fmtp:96 packetization-mode=1;profile-level-id=42C014;sprop-parameter-sets=Z0LAFNoFB+hAAAADAEAAAAwDxQqo,aM4PyA==
Base-64 strings decodes to this:
00000000 67 42 c0 14 da 05 07 e8 40 00 00 03 00 40 00 00 |gB......#....#..|
00000010 0c 03 c5 0a a8
00000000 68 ce 0f c8 |h...|
which partially matches the data in mkv's CodecPrivate.
Extracted conversion from raw to CodecPrivate from ffmpeg:
/*
* AVC helper functions for muxers
* Copyright (c) 2006 Baptiste Coudurier <baptiste.coudurier#smartjog.com>
* Modified by _Vi: stand-alone version (without ffmpeg)
*
* This file is based on the code from FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <string.h>
#include <stdio.h>
#define assert(x) if(!(x)) { fprintf(stderr, "Assertion failed...\n"); return -1; }
#ifndef AV_RB24
# define AV_RB24(x) \
((((const uint8_t*)(x))[0] << 16) | \
(((const uint8_t*)(x))[1] << 8) | \
((const uint8_t*)(x))[2])
#endif
#ifndef AV_RB32
# define AV_RB32(x) \
(((uint32_t)((const uint8_t*)(x))[0] << 24) | \
(((const uint8_t*)(x))[1] << 16) | \
(((const uint8_t*)(x))[2] << 8) | \
((const uint8_t*)(x))[3])
#endif
#define avio_w8(pb, x) *(*pb)++ = x;
#define avio_wb16(pb, x) *(*pb)++ = ((x)>>8); *(*pb)++ = x&0xFF;
#define avio_wb32(pb, x) *(*pb)++ = ((x)>>24); \
*(*pb)++ = ((x)>>16)&0xFF; \
*(*pb)++ = ((x)>>8)&0xFF; \
*(*pb)++ = ((x)>>0)&0xFF;
#define avio_write(pb, b, l) memcpy((*pb), b, l); (*pb)+=(l);
typedef unsigned char uint8_t;
typedef int intptr_t;
typedef unsigned long uint32_t;
static const uint8_t *ff_avc_find_startcode_internal(const uint8_t *p, const uint8_t *end)
{
const uint8_t *a = p + 4 - ((intptr_t)p & 3);
for (end -= 3; p < a && p < end; p++) {
if (p[0] == 0 && p[1] == 0 && p[2] == 1)
return p;
}
for (end -= 3; p < end; p += 4) {
uint32_t x = *(const uint32_t*)p;
// if ((x - 0x01000100) & (~x) & 0x80008000) // little endian
// if ((x - 0x00010001) & (~x) & 0x00800080) // big endian
if ((x - 0x01010101) & (~x) & 0x80808080) { // generic
if (p[1] == 0) {
if (p[0] == 0 && p[2] == 1)
return p;
if (p[2] == 0 && p[3] == 1)
return p+1;
}
if (p[3] == 0) {
if (p[2] == 0 && p[4] == 1)
return p+2;
if (p[4] == 0 && p[5] == 1)
return p+3;
}
}
}
for (end += 3; p < end; p++) {
if (p[0] == 0 && p[1] == 0 && p[2] == 1)
return p;
}
return end + 3;
}
const uint8_t *ff_avc_find_startcode(const uint8_t *p, const uint8_t *end){
const uint8_t *out= ff_avc_find_startcode_internal(p, end);
if(p<out && out<end && !out[-1]) out--;
return out;
}
int ff_avc_parse_nal_units(unsigned char **pb, const uint8_t *buf_in, int size)
{
const uint8_t *p = buf_in;
const uint8_t *end = p + size;
const uint8_t *nal_start, *nal_end;
size = 0;
nal_start = ff_avc_find_startcode(p, end);
while (nal_start < end) {
while(!*(nal_start++));
nal_end = ff_avc_find_startcode(nal_start, end);
avio_wb32(pb, nal_end - nal_start);
avio_write(pb, nal_start, nal_end - nal_start);
size += 4 + nal_end - nal_start;
nal_start = nal_end;
}
return size;
}
int ff_avc_parse_nal_units_buf(const unsigned char *buf_in, unsigned char **buf, int *size)
{
unsigned char *pbptr = *buf;
ff_avc_parse_nal_units(&pbptr, buf_in, *size);
*size = pbptr - *buf;
return 0;
}
int my_isom_write_avcc(unsigned char **pb, const uint8_t *data, int len)
{
unsigned char tmpbuf[4000];
if (len > 6) {
/* check for h264 start code */
if (AV_RB32(data) == 0x00000001 ||
AV_RB24(data) == 0x000001) {
uint8_t *buf=tmpbuf, *end, *start;
uint32_t sps_size=0, pps_size=0;
uint8_t *sps=0, *pps=0;
int ret = ff_avc_parse_nal_units_buf(data, &buf, &len);
if (ret < 0)
return ret;
start = buf;
end = buf + len;
/* look for sps and pps */
while (buf < end) {
unsigned int size;
uint8_t nal_type;
size = AV_RB32(buf);
nal_type = buf[4] & 0x1f;
if (nal_type == 7) { /* SPS */
sps = buf + 4;
sps_size = size;
} else if (nal_type == 8) { /* PPS */
pps = buf + 4;
pps_size = size;
}
buf += size + 4;
}
assert(sps);
assert(pps);
avio_w8(pb, 1); /* version */
avio_w8(pb, sps[1]); /* profile */
avio_w8(pb, sps[2]); /* profile compat */
avio_w8(pb, sps[3]); /* level */
avio_w8(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
avio_w8(pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */
avio_wb16(pb, sps_size);
avio_write(pb, sps, sps_size);
avio_w8(pb, 1); /* number of pps */
avio_wb16(pb, pps_size);
avio_write(pb, pps, pps_size);
} else {
avio_write(pb, data, len);
}
}
return 0;
}
#define H264PRIVATE_MAIN
#ifdef H264PRIVATE_MAIN
int main() {
unsigned char data[1000];
int len = fread(data, 1, 1000, stdin);
unsigned char output[1000];
unsigned char *output_f = output;
my_isom_write_avcc(&output_f, data, len);
fwrite(output, 1, output_f - output, stdout);
return 0;
}
#endif
Inserting "00 00 00 01" before each base-64-decoded block and feeding it into that program outputs CodecPrivate:
$ printf '\x00\x00\x00\x01'\
'\x67\x42\xc0\x14\xda\x05\x07\xe8\x40\x00\x00\x03\x00\x40\x00\x00\x0c\x03\xc5\x0a\xa8'\
'\x00\x00\x00\x01'\
'\x68\xce\x0f\xc8' | ./avc_to_mkvcodecpriv | hd
00000000 01 42 c0 14 ff e1 00 15 67 42 c0 14 da 05 07 e8 |.B......gB......|
00000010 40 00 00 03 00 40 00 00 0c 03 c5 0a a8 01 00 04 |#....#..........|
00000020 68 ce 0f c8 |h...|
00000024