3

Please consider this (non-homework) exercise of converting a slash-notation (e.g. 24, 30) to a subnet mask.

When I copy a BitArray to byte[], the internal ordering of the BitArray leads to incorrect output.

For instance, with an input of numberOfSetBits=24, ToString() should return 255.255.255.0 (this works because the bits are symmetrical). However, an input of 30 results in 255.255.255.63 instead of the expected 255.255.255.252. Yes, I realize that that's just the way a BitArray handles it's children (there's an old discussion about the issue, unfortunately without a solution, just a never-ending argument over why one ordering would be better).

But how, for the love of god, can I get this code to treat 1111 1100 (=252) for what it is instead of mangling it to 0011 1111 (=63)? I believe I'll have to change the order in which I'm adding the bits in the first place, but I can't get it to work.

public class SubnetMask
{
    private byte[] parts = new byte[4];

    public static SubnetMask FromSlash(int numberOfSetBits)
    {
        BitArray bits = new BitArray(32);
        for (int i = 0; i < numberOfSetBits; i++)
        {
            bits[i] = true;
        }
        return new SubnetMask(bits);
    }

    private SubnetMask(BitArray bits)
    {
        bits.CopyTo(parts, 0);
    }

    public override string ToString()
    {
        return string.Join(".", parts);
    }
}

Thank you.

Community
  • 1
  • 1
Adam
  • 15,537
  • 2
  • 42
  • 63

2 Answers2

2

Wouldn't this just be fixed by doing:

int bitPlace = 32;
for (int i = 0; i < numberOfSetBits; i++)
{
    bits[--bitPlace] = true;
}

This sets the high-order bits (rather than the low-order bits). The reasoning here is precisely what is mentioned in the answer you linked- BitArray stores the least-significant digits in the lowest index, so the bit array 11111000000000 below is [indexed] thus:

(Most Significant)                  (Least Significant)
 1    1    1    1    1   0   0   0   0   0   0   0   0   0
[14] [13] [11] [10] [9] [8] [7] [6] [5] [4] [3] [2] [1] [0]
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • this does indeed work, however it requires this change in the `SubnetMask` constructor: `parts = parts.Reverse().ToArray();` to fix the ordering of the bytes. – Adam May 29 '12 at 22:04
  • thanks for your effort though - I upvoted, but I can only set one post as the answer. – Adam May 29 '12 at 22:10
2

You don't need a BitArray, you can create the mask from shifting an integer, and use BitConverter.GetBytes to get it as bytes:

public static SubnetMask FromSlash(int numberOfSetBits) {
  int mask = numberOfSetBits == 0 ? 0 : -1 << (32 - numberOfSetBits);
  byte[] bits = BitConverter.GetBytes(mask);
  if (BitConverter.IsLittleEndian) Array.Reverse(bits);
  return new SubnetMask(bits);
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • interestingly, it doesn't work for an input of 0. Any explanation why left shifting an integer by 32 bits leaves me with the original integer? – Adam Jun 01 '12 at 16:29
  • @codesparkle: Ah... the shift only uses five bits of the second operand, i.e. `x << (y & 0x1f)`. "The count is masked to 5 bits": http://jsimlo.sk/docs/cpu/index.php/shl.html So you need to do an extra check for that. – Guffa Jun 01 '12 at 19:39
  • that's annoying.. I wonder why it has to be limited that way. could you update your answer to reflect that? e.g. `numberOfSetBits == 0 ? 0 : -1 << (32 - numberOfSetBits)` – Adam Jun 02 '12 at 10:49