3

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

Woof
  • 1,207
  • 1
  • 11
  • 21
  • @matt, as I said, I'm using the code example from the MikroTik wiki. Could you suggest a code that will work properly? – Woof Jul 19 '19 at 02:30
  • @matt, well, it's definitely stopped crashing, but socket also stopped answer to that command. Small commands work though. – Woof Jul 19 '19 at 03:23
  • 1
    Okay, now that you've shown working code in JavaScript, the solution is pretty easy. – matt Jul 20 '19 at 22:50

1 Answers1

2

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141