2

I grabbed a piece of code from the internet (https://github.com/Cinegy/TsDecoder) and I'm trying to understand with it is doing.

This is the constructor:

public SatelliteDeliverySystemDescriptor(byte[] stream, int start) 
{
    Frequency =
        $"{(stream[start + 2] >> 4) & 0x0F}{stream[start + 2] & 0x0F}{(stream[start + 3] >> 4) & 0x0F}{stream[start + 3] & 0x0F}{(stream[start + 4] >> 4) & 0x0F}{stream[start + 4] & 0x0F}{(stream[start + 5] >> 4) & 0x0F}{stream[start + 5] & 0x0F}";
    OrbitalPosition =
        $"{(stream[start + 6] >> 4) & 0x0F}{stream[start + 6] & 0x0F}{(stream[start + 7] >> 4) & 0x0F}{stream[start + 7] & 0x0F}";
    WestEastFlag = ((stream[start + 8] >> 7) & 0x01) == 0x01;
    Polarization = (byte)((stream[start + 8] >> 5) & 0x03);
    RollOff = (byte)((stream[start + 8] >> 3) & 0x03);
    ModulationSystem = ((stream[start + 8] >> 2) & 0x01) == 0x01;
    Modulation = (byte)(stream[start + 8] & 0x03);
    SymbolRate =
        $"{(stream[start + 9] >> 4) & 0x0F}{stream[start + 9] & 0x0F}{(stream[start + 10] >> 4) & 0x0F}{stream[start + 10] & 0x0F}{(stream[start + 11] >> 4) & 0x0F}{stream[start + 11] & 0x0F}{(stream[start + 12] >> 4) & 0x0F}";
    FECInner = (byte)(stream[start + 12] & 0x0F);
}

What i did

var hexString = "430B1200000025E0C610500003";
var hexBytes = Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
var descriptor = new SatelliteDeliverySystemDescriptor(hexBytes, 0);

NOTE: "hexBytes" is:

  [0]: 67
  [1]: 11
  [2]: 18
  [3]: 0
  [4]: 0
  [5]: 0
  [6]: 37
  [7]: 224
  [8]: 198
  [9]: 16
  [10]: 80
  [11]: 0
  [12]: 3

This is the output:

    Frequency = 12000000
    OrbitalPosition = 25140
    WestEastFlag = true
    Polarization = 2
    RollOff = 0
    ModulationSystem = true
    Modulation = 2
    SymbolRate = 1050000
    FECInner = 3

My question(s)

Why is the programmer doing e.g. this in order to get the result? What is the approch? Why he is doing all these shiftings AND BITwise AND operators?

        $"{(stream[start + 2] >> 4) & 0x0F}{stream[start + 2] & 0x0F}{(stream[start + 3] >> 4) & 0x0F}{stream[start + 3] & 0x0F}{(stream[start + 4] >> 4) & 0x0F}{stream[start + 4] & 0x0F}{(stream[start + 5] >> 4) & 0x0F}{stream[start + 5] & 0x0F}";

In order words: What is exactly happening in this piece of code and is there not a better readable approach?

Thanks!

3
  • I grabbed a piece of code from the internet and I'm trying to understand with it is doing. At least point us to where you got it from, so we can check the context of what it is trying to achieve. Commented Feb 26, 2018 at 9:29
  • Well it's impossible to tell what the programmer intended to do without any description of the problem. Do you understand the syntax of the lines of code you're trying to decypher? Commented Feb 26, 2018 at 9:35
  • Its checking a bunch of bitwize flags and values at certain bit positions at certain bytes essentially, and id hazzard a guess, the description of those flags are in your code Commented Feb 26, 2018 at 9:36

1 Answer 1

1

Bitwise operators are usually used to work with individual bits inside a byte. In C# there is BitArray class which allows you to work with bits, but I'd say it's rarely used, probably because bitwise operators became common practice for this task (and also BitArray is useless for many operations which can be done with bitwise operators anyway).

In your example there is byte array which encodes certain data, but individual pieces of this data are not represented by a whole byte value. Instead - one byte might encode several pieces of information in its individual bits.

If look at this operation:

stream[start + 2] >> 4) & 0x0F

It reads first half (first 4 bits) of 3rd byte as a number. In your example 3rd byte is 0x12 which in binary form is 0001 0010. Right shift, well, shifts given number of bits to the right:

0001 0010 >> 4 = 0000 0001

Now left half of byte is moved to the right half, and left half became zero. As you see, now left half 0001 and whole byte 0000 0001 represent the same number - that is number "1". That was the goal of >> 4 operation. Doing & 0x0F after that is useless and does nothing, so I will omit explanation of that.

Now we got left half and need to get right half. We do that with

stream[start + 2] & 0x0F

Bitwise AND, as you probably know, leaves only bits which are set ("1") in both numbers:

0001 0010 &
0000 1111 < this is 0x0F in binary
0000 0010

By doing that we zeroed left half of the byte and now right half (0010) and number we got (0000 0010) represent the same number ("2").

So in result we got left half ("1" in decimal) and right half ("2" in decimal). You can see this is used to represent Frequency, OrbitalPosition and several other properties in your case.

Let's see at this operation:

((stream[start + 8] >> 7) & 0x01) == 0x01

If we have byte abcd efjk and do right shift by 7 bits, we have:

abcd efjk >> 7
0000 000a

So we got value of leftmost bit (a). Doing & 0x01 is again useless and does nothing, so can be omitted. So we can conclude that WestEastFlag flag is stored in leftmost bit of 9th byte. If that bit is 1 - flag is set, otherwise - not set. Bitshift by 7 bits allows us to get to this flag.

You can apply the same logic to other operations:

(stream[start + 8] >> 5) & 0x03

abcd efkj >> 5
0000 0abc

0000 0abc &
0000 0011 < 0x03
0000 00bc

So this one allows us to get to bits b and c (6 and 7 if count from the right), in which, according to that code, Polarization is stored.

For better understanding you can first avoid unnecessary operations (as described above), and also use binary literals:

// easier to undrestand what's going on, compared to 0x03
(stream[start + 8] >> 5) & 0b0000_0011 // < need last two bits after the shift

For readability for particular set of operations used in this question you can create extension method like that (warning - proper test it if will use, not properly tested):

public static class ByteExtensions {
    public static byte GetBits(this byte x, byte from, byte to) {
        if (to > 8)
            throw new ArgumentOutOfRangeException(nameof(to));
        if (to < from)
            throw new ArgumentOutOfRangeException(nameof(from));
        x >>= (from - 1);
        x &= (byte)(0xFF >> (7 - (to - from)));
        return x;
    }
}

Then operations described above can be performed using it. For example:

stream[start + 2] >> 4) & 0x0F // get left half

becomes

stream[start + 2].GetBits(5, 8) // get left half (so, bits 5,6,7,8)

And

(stream[start + 8] >> 5) & 0x03 // get bits 6,7

Becomes

stream[start + 8].GetBits(6,7)
Sign up to request clarification or add additional context in comments.

2 Comments

Yess!! This was the explanation i was waiting for. THanks!
@SanderPham I've also added extension method which might help with readability.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.