Scott Jibben
Scott Jibben

Reputation: 2287

cffunction/cfargument pass unsigned int32

I'm working on building a function that will convert an unsigned int32 to an ipv4 address and it all works great until I test with the upper half of the range.

<cffunction name="Int32ToIPv4" returntype="string" output="false" access="public" hint="returns IPv4 address for int32 number">
    <cfargument name="nInt32" type="numeric" required="yes" hint="int32 to convert to ipv4 address">

    <cfreturn
        bitAnd((arguments.nInt32 / 16777216), 255) & "."
        & bitAnd((arguments.nInt32 / 65536), 255) & "."
        & bitAnd((arguments.nInt32 / 256), 255) & "."
        & bitAnd(arguments.nInt32, 255)
    >
</cffunction>

ColdFusion appears to interpret type="numeric" to be a signed 32-bit integer.

This example works:

Int32ToIPv4(nInt32 = 1181772947) = #Int32ToIPv4(nInt32 = 1181772947)# (expected value = 70.112.108.147)<br>

This example fails:

Int32ToIPv4(nInt32 = 3401190660) = #Int32ToIPv4(nInt32 = 3401190660)# (expected value = 202.186.13.4)<br>

The error message is: "Cannot convert the value 3.40119066E9 to an integer because it cannot fit inside an integer."

Will I have to pass this number in as a string and convert it to a Java unsigned int before using it?

I'll want to do something similar with ipv6 addresses (unsigned 128-bit integer) at some point. Any advice for this type of data-type would be appreciated.

Upvotes: 4

Views: 203

Answers (2)

Scott Jibben
Scott Jibben

Reputation: 2287

I did come up with a native Java solution that involved passing in an unsigned 32-bit int. So, @Leigh is correct about cfargument working properly.

<cffunction name="UInt32ToIPv4" returntype="string" output="no" access="public" hint="returns IPv4 address for uint32 number">
    <cfargument name="nUInt32" type="numeric" required="yes" hint="uint32 to convert to ipv4 address">

    <cfset local['JavaLangInteger'] = CreateObject("java", "java.lang.Integer")>
    <cfset local['JavaMathBigInteger'] = CreateObject("java", "java.math.BigInteger")>
    <cfset local['JavaNetInetAddress'] = CreateObject("java", "java.net.InetAddress")>

    <cfif arguments.nUInt32 EQ 0>
        <cfreturn "">
    </cfif>

    <cftry>
        <cfset local['vcIPv4'] = local.JavaNetInetAddress.getByAddress(local.JavaMathBigInteger.valueOf(local.JavaLangInteger.parseUnsignedInt(arguments.nUInt32)).toByteArray())>
        <cfreturn reReplace(local.vcIPv4, "[^0-9\.]", "", "all")>
        <cfcatch type="any">
            <cfreturn "">
        </cfcatch>
    </cftry>
</cffunction>

Let me know if there are any problems with this solution as I think that I will use it instead of a public java library.

I have written functions for ipv6 to handle the conversions (unsigned 128-bit int to/from ipv6). Not sure that there is an interest in those functions. If there is, I'll create separate questions/answers with that code.

Upvotes: 2

Leigh
Leigh

Reputation: 28873

It is actually bitAnd() imposing the 32 bit integer limitation, not cfargument.

Interestingly, if you check each calculation separately, the first three (3) work fine. Due to the division, the values passed into bitAnd() are actually small enough to fit inside an INT. However, the last one uses the raw value which is obviously too big, and that is what causes the error with this specific number.

 bitAnd(  (3401190660 / 16777216), 255 )  // Actual => 202.7267611032, 255
 bitAnd(  (3401190660 / 65536), 255 ) // Actual => 51898.05084233, 255
 bitAnd(  (3401190660 / 256), 255 )  // Actual =>13285901.0156, 255
 bitAnd(3401190660, 255) // Too big!

Since most of the relevant math functions in CF are limited to 32 bit integers, I am not sure if (or how simply) it can be done with CF alone. Assuming you are using Java 8+, you also have access to the new unsigned methods like Integer.parseUnsignedInt(String).

Personally I think this would be a great learning exercise. However, unless you are very familiar with the details of signed versus unsigned and how it relates to IP4 and IP6 addresses, you might just want to use an existing java library to avoid common pitfalls. Such as Guava's InetAdress:

// IP4
input = 3401190660;
uint = createObject("java", "java.lang.Integer").parseUnsignedInt(input);
inet = createObject("Java", "com.google.common.net.InetAddresses");
result = inet.fromInteger(uint).getHostAddress();

Upvotes: 3

Related Questions