Fatal error: Not enough bits to represent the passed value

1

Trying to use Mikrotik API library written in Swift: https://wiki.mikrotik.com/wiki/API_in_Swift

It works well, when I'm sending small commands

However, If I will try to send large script string, I'm getting error:

Fatal error: Not enough bits to represent the passed value

The code that crashes:

private func writeLen(_ command : String) -> Data {
    let data = command.data(using: String.Encoding.utf8)
    var len = data?.count ?? 0
    var dat = Data()

    if len < 0x80 {
        dat.append([UInt8(len)], count: 1)
    }else if len < 0x4000 {
        len = len | 0x8000;
        dat.append(Data(bytes: [UInt8(len >> 8)]))
        dat.append(Data(bytes: [UInt8(len)]))
    }else if len < 0x20000 {
        len = len | 0xC00000;
        dat.append(Data(bytes: [UInt8(len >> 16)]))
        dat.append(Data(bytes: [UInt8(len >> 8)]))
        dat.append(Data(bytes: [UInt8(len)]))
    }
    else if len < 0x10000000 {
        len = len | 0xE0000000;
        dat.append(Data(bytes: [UInt8(len >> 24)]))
        dat.append(Data(bytes: [UInt8(len >> 16)]))
        dat.append(Data(bytes: [UInt8(len >> 8)]))
        dat.append(Data(bytes: [UInt8(len)]))
    }else{
        dat.append(Data(bytes: [0xF0]))
        dat.append(Data(bytes: [UInt8(len >> 24)]))
        dat.append(Data(bytes: [UInt8(len >> 16)]))
        dat.append(Data(bytes: [UInt8(len >> 8)]))
        dat.append(Data(bytes: [UInt8(len)]))
    }

    return dat
}

The fatal error appears in this part:

else if len < 0x4000 {
    len = len | 0x8000;
    dat.append(Data(bytes: [UInt8(len >> 8)]))
    dat.append(Data(bytes: [UInt8(len)]))
}

at line:

dat.append(Data(bytes: [UInt8(len)]))

Data size at this moment is 1072 bytes and len equals to 33840, UInt8 cannot be initiated with that len value.

How can I edit the code to avoid the error?

I'm using Swift 4.2

EDIT:

Here is an example of the same logic but written in JavaScript

module.exports.encodeString = function encodeString(s) {
var data = null;
var len = Buffer.byteLength(s);
var offset = 0;
if (len < 0x80) {
    data = new Buffer(len + 1);
    data[offset++] = len;
} else if (len < 0x4000) {
    data = new Buffer(len + 2);
    len |= 0x8000;
    data[offset++] = (len >> 8) & 0xff;
    data[offset++] = len & 0xff;
} else if (len < 0x200000) {
    data = new Buffer(len + 3);
    len |= 0xC00000;
    data[offset++] = (len >> 16) & 0xff;
    data[offset++] = (len >> 8) & 0xff;
    data[offset++] = len & 0xff;
} else if (len < 0x10000000) {
    data = new Buffer(len + 4);
    len |= 0xE0000000;
    data[offset++] = (len >> 24) & 0xff;
    data[offset++] = (len >> 16) & 0xff;
    data[offset++] = (len >> 8) & 0xff;
    data[offset++] = len & 0xff;
} else {
    data = new Buffer(len + 5);
    data[offset++] = 0xF0;
    data[offset++] = (len >> 24) & 0xff;
    data[offset++] = (len >> 16) & 0xff;
    data[offset++] = (len >> 8) & 0xff;
    data[offset++] = len & 0xff;
}
data.utf8Write(s, offset);
return data;
};

Maybe someone sees the difference

swift
mikrotik
asked on Stack Overflow Jul 19, 2019 by Woof • edited Jul 19, 2019 by Woof

1 Answer

1

Thanks for the JavaScript translation. It clearly shows the problem, since the Swift version does not resemble it.

Let's take this stretch of the JavaScript, as it is the part you are stumbling over in Swift:

} else if (len < 0x4000) {
    data = new Buffer(len + 2);
    len |= 0x8000;
    data[offset++] = (len >> 8) & 0xff;
    data[offset++] = len & 0xff;
}

That is "translated" in Swift like this:

} else if len < 0x4000 {
    len = len | 0x8000;
    dat.append(Data(bytes: [UInt8(len >> 8)]))
    dat.append(Data(bytes: [UInt8(len)]))
} 

Well, you can see at once that they are not at all the same. In the last line, the Swift version has forgotten the & 0xff.

If you put that in, everything starts working. And we can make it look a lot more like the JavaScript original too:

} else if len < 0x4000 {
    len |= 0x8000;
    dat.append(Data(bytes: [UInt8(len >> 8)]))
    dat.append(Data(bytes: [UInt8(len & 0xff)]))
}

So I'd say, yes, use the JavaScript as a guide and you'll be fine. If that last line doesn't feel "swifty" enough to you, then write it like this:

    dat.append(Data(bytes: [UInt8(truncatingIfNeeded: len)]))

It's exactly the same result.

I don't guarantee that everything will work perfectly after you make those changes (the Swift code you showed still does not look to me like it does the same thing as the JavaScript), but at least the part where we write the length bytes into the start of the Data will work correctly.

answered on Stack Overflow Jul 20, 2019 by matt • edited Jul 20, 2019 by matt

User contributions licensed under CC BY-SA 3.0