I try to receive data from socket and i have problem.
Error telling:
System.ArgumentOutOfRangeException: 'Index and count must refer to a location in the buffer.
I don't why i have this error and i have question of what is "index" for the function Encoding.ASCII.GetString(byte[] bytes, int index, int count)
Client used: https://github.com/AbleOpus/NetworkingSamples/tree/master/BasicAsyncServer
Server used: https://github.com/AbleOpus/NetworkingSamples/tree/master/BasicAsyncServer
Error is the function ToByteArray: https://github.com/AbleOpus/NetworkingSamples/blob/master/BasicAsyncServer/PersonPackage.cs
What i have in this function (the problem)
public string Username { get; set; }
public string HashPassword { get; set; }
public string HWID { get; set; }
public Command(string username, string password, string hwid)
{
Username = username;
HashPassword = password;
HWID = hwid;
}
public Command(byte[] data)
{
int usernameLenght = BitConverter.ToInt32(data, 0);
Username = Encoding.ASCII.GetString(data, 1, usernameLenght);
int passwordLenght = BitConverter.ToInt32(data, 2);
HashPassword = Encoding.ASCII.GetString(data, 3, passwordLenght); //I got the exception here
int hwid = BitConverter.ToInt32(data, 4);
HWID = Encoding.ASCII.GetString(data, 5, hwid);
}
public byte[] ToByteArray()
{
List<byte> byteList = new List<byte>();
byteList.AddRange(BitConverter.GetBytes(Username.Length));
byteList.AddRange(Encoding.ASCII.GetBytes(Username));
byteList.AddRange(BitConverter.GetBytes(HashPassword.Length));
byteList.AddRange(Encoding.ASCII.GetBytes(HashPassword));
byteList.AddRange(BitConverter.GetBytes(HWID.Length));
byteList.AddRange(Encoding.ASCII.GetBytes(HWID));
return byteList.ToArray();
}
CodePudding user response:
The problem
GetString(byte[] bytes, int index, int count)
, takes count
number of bytes in bytes
, starting at position index
, and decodes them. This is important: index
is a byte position within the input array. BitConverter.ToInt32
works the same for its second parameter (but here you don't need a count, because that is always four since this is the size in bytes of an Int32
). This means the position you pass to GetString
(and also to ToInt32
) is in bytes, not in number of fields, which is what you seem to use it as. When you read back those values, you need to track which specific byte is the start of any given value.
But first off, why do you get this exception? Well, because you're not reading the right thing.
int passwordLenght = BitConverter.ToInt32(data, 2);
This reads 4 bytes in the middle of nowhere (technically, the second half of the username length and then the first two characters of the username itself) and interprets it as an int. It's highly likely to give you a very big number, and so when you call Encoding.ASCII.GetString(data, 3, passwordLenght);
, you end up trying to read more bytes than there are in the array, and thus, you get an error.
So let's look at how we can fix this.
First solution: fix the offsets
So, let's look at your little protocol, using username as an example:
byteList.AddRange(BitConverter.GetBytes(Username.Length));
byteList.AddRange(Encoding.ASCII.GetBytes(Username));
BitConverter.GetBytes(Username.Length)
returns four bytes, because that's the size of an int
. After that you append the string's contents, encoded in ASCII. So when you want to read it back:
int usernameLenght = BitConverter.ToInt32(data, 0);
Username = Encoding.ASCII.GetString(data, 4, usernameLenght); // <-- pass 4, not 1
You need to pass 4, not 1, to GetString
, because your username's data starts four bytes after the start of the data.
Then, when you want to read the password, you need to first read the length at the position where the username's data ends. So:
int passwordLenght = BitConverter.ToInt32(data, usernameLenght 4);
HashPassword = Encoding.ASCII.GetString(data, usernameLenght 8, passwordLenght);
The password begins at usernameLenght 4
bytes (the 4
is because you need to offset by the four bytes used for the length`). And then the password itself you need to add another 4 (for a total of 8) to skip over the password length itself.
Keep going for hwid
.
An improvement : track offset with a variable
Another approach would be to use a variable to track where you're at:
int offset = 0;
int usernameLenght = BitConverter.ToInt32(data, offset);
offset = 4;
Username = Encoding.ASCII.GetString(data, offset , usernameLenght);
offset = usernameLenght;
int passwordLenght = BitConverter.ToInt32(data, offset);
offset = 4;
HashPassword = Encoding.ASCII.GetString(data, offset, passwordLenght);
offset = passwordLenght;
// etc.
This is much less error prone.
A refactor: put that into a method
With that change, we now have a very clear pattern, which means we could even refactor that into a cute little method:
string ReadNextString(byte[] data, ref int offset)
{
var length = BitConverter.ToInt32(data, offset);
offset = 4;
var value = Encoding.ASCII.GetString(data, offset, length);
offset = length;
return value;
}
int offset = 0;
Username = ReadNext(data, ref offset);
HashPassword = ReadNext(data, ref offset);
HWID = ReadNext(data, ref offset);
An implementation using spans is left as an exercise to the reader.
A note about character lengths vs byte lengths
While I'm here, I've noticed that you write the length of the string, in characters, but you read it as the length in bytes. This will work as long as you stick to ASCII, because those two values will be identical, but with other encodings it'll lead to problems. A solution would be to first convert the string to bytes, write the length of that byte array, and then the array itself:
var usernameData = Encoding.ASCII.GetBytes(Username);
byteList.AddRange(BitConverter.GetBytes(usernameData.Length));
byteList.AddRange(usernameData);
CodePudding user response:
Encoding.ASCII.GetString(byte[] bytes, int index, int count)
The first argument is a byte array that will be decoded, returning the string representation. I'm sure you already know this.
The second argument represents some index of that byte array. It is index to start decoding from. So, if you want to decode the whole byte array you would want to start from index 0.
The third argument represents the number of bytes to decode. If you would like to decode the whole array, your array has 14 bytes, and you are starting from index 0, your count would be 14.
System.ArgumentOutOfRangeException: 'Index and count must refer to a location in the buffer.
This would refer to one of two issues, or both.
- You are passing an index as an argument that does not exist in your byte array. If your byte array is of length 4 and you are passing in index 7, then that index is out of range.
- You are passing a count as an argument that causes the function to go out of bounds of the array. If length is 4, you pass index as 2, and you pass count as 4, then you go out of bounds because you are starting at index 2 and decoding 4 bytes. (You would be out of bounds by 2 array indices)