Oracle PL/SQL versions of INET6_ATON and NTOA functions? - oracle

Any have any good code for converting a IPv6 address string into an integer? Converting IPv4 seems to be fairly easy, with the one format. However, IPv6 has several different formats to show an address:
XXXX:XXXX:XXXX:XXXX::
XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX
XXXX:XXX:XXXX:0:0:XXXX:XXX:XXXX
XXXX:XXX:XXXX::XXXX:XXX:XXXX
::ffff:XXXX:XXX (IPv4 in v6 format)
::ffff:###.#.#.### (also valid IPv4 in v6 format)
I'd like to be able to take one of these strings and translate it into an INTEGER for IP-to-network matching, and allow for any of these formats as the input.

Ended up rolling my own. Also realized that Oracle's 126-bit INTEGER is not enough bits for IPv6's 128-bit addresses. Frankly, I don't know how the original C library's INET6_ATON (or INET_PTON) does it, considering that I've never heard of a 16-byte integer.
I ended up with a 32-byte hex string, which means I have to do some fancy "half-string" math on nettohex and use SUBSTR for the FBIs to work correctly. (Blasted PL/SQL doesn't allow for "RETURN CHAR(32)"...)
Overall, though, it works well, works in all formats, and allows for index-based character comparisons to find out if an IP address is within an IP range.
Here's the full code:
CREATE OR REPLACE FUNCTION ipguess(
ip_string IN VARCHAR2
) RETURN NATURAL
DETERMINISTIC
IS
BEGIN
-- Short-circuit the most popular, and also catch the special case of IPv4 addresses in IPv6
IF REGEXP_LIKE(ip_string, '\d{1,3}(\.\d{1,3}){3}') THEN RETURN 4;
ELSIF REGEXP_LIKE(ip_string, '[[:xdigit:]]{0,4}(\:[[:xdigit:]]{0,4}){0,7}') THEN RETURN 6;
ELSE RETURN NULL;
END IF;
END ipguess;
CREATE OR REPLACE FUNCTION iptohex(
ip_string IN VARCHAR2
) RETURN CHAR -- INTEGER only holds 126 binary digits, IPv6 has 128
DETERMINISTIC
IS
iptype NATURAL := ipguess(ip_string);
ip VARCHAR2(32);
ipwork VARCHAR2(64);
d INTEGER;
q VARCHAR2(3);
BEGIN
IF iptype = 4 THEN
-- Sanity check
ipwork := REGEXP_SUBSTR(ip_string, '\d{1,3}(\.\d{1,3}){3}');
IF ipwork IS NULL THEN RETURN NULL; END IF;
-- Starting prefix
-- NOTE: 2^48 - 2^32 = 281470681743360 = ::ffff:0.0.0.0
-- (for compatibility with IPv4 addresses in IPv6)
ip := '00000000000000000000ffff';
-- Parse the input
WHILE LENGTH(ipwork) IS NOT NULL
LOOP
d := INSTR(ipwork, '.'); -- find the dot
IF d > 0 THEN -- isolate the decimal octet
q := SUBSTR(ipwork, 1, d - 1);
ipwork := SUBSTR(ipwork, d + 1);
ELSE
q := ipwork;
ipwork := '';
END IF;
-- convert to a hex string
ip := ip || TO_CHAR(TO_NUMBER(q), 'FM0x');
END LOOP;
ELSIF iptype = 6 THEN
-- Short-circuit "::" = 0
IF ip_string = '::' THEN RETURN LPAD('0', 32, '0'); END IF;
-- Sanity check
ipwork := REGEXP_SUBSTR(ip_string, '[[:xdigit:]]{0,4}(\:[[:xdigit:]]{0,4}){0,7}');
IF ipwork IS NULL THEN RETURN NULL; END IF;
-- Replace leading zeros
-- (add a bunch to all of the pairs, then remove only the required ones)
ipwork := REGEXP_REPLACE(ipwork, '(^|\:)([[:xdigit:]]{1,4})', '\1000\2');
ipwork := REGEXP_REPLACE(ipwork, '(^|\:)0+([[:xdigit:]]{4})', '\1\2');
-- Groups of zeroes
-- (total length should be 32+Z, so the gap would be the zeroes)
ipwork := REPLACE(ipwork, '::', 'Z');
ipwork := REPLACE(ipwork, ':');
ipwork := REPLACE(ipwork, 'Z', LPAD('0', 33 - LENGTH(ipwork), '0'));
ip := LOWER(ipwork);
ELSE
RETURN NULL;
END IF;
RETURN ip;
END iptohex;
CREATE OR REPLACE FUNCTION nettohex(
ip_string IN VARCHAR2,
cidr IN NATURALN,
is_end IN SIGNTYPE DEFAULT 0
) RETURN CHAR
DETERMINISTIC
IS
iptype NATURAL := ipguess(ip_string);
iphex CHAR(32) := iptohex(ip_string);
iphalf1 CHAR(16) := SUBSTR(iphex, 1, 16);
iphalf2 CHAR(16) := SUBSTR(iphex, 17);
ipwork CHAR(16) := iphalf2;
cidr_exp INTEGER := 2 ** (iptype + 1) - cidr;
ipint INTEGER;
subnet INTEGER;
is_big SIGNTYPE := 0;
BEGIN
-- Sanity checks
IF iptype IS NULL THEN RETURN NULL;
ELSIF iphex IS NULL THEN RETURN NULL;
END IF;
IF cidr_exp >= 64 THEN is_big := 1;
ELSIF cidr_exp = 0 THEN RETURN iphex; -- the exact IP, such as /32 on IPv4
ELSIF cidr_exp < 0 THEN RETURN NULL;
ELSIF cidr_exp > 128 THEN RETURN NULL;
END IF;
-- Change some variables around if we are working with the first/largest half
IF is_big = 1 THEN
ipwork := iphalf1;
iphalf2 := TO_CHAR((2 ** 64 - 1) * is_end, 'FM0xxxxxxxxxxxxxxx'); -- either all 0 or all F
cidr_exp := cidr_exp - 64;
END IF;
-- Normalize IP to divisions of CIDR
subnet := 2 ** cidr_exp;
ipint := TO_NUMBER(ipwork, 'FM0xxxxxxxxxxxxxxx');
-- if is_end = 1 then add one net range (then subtract one IP) to get the ending range
ipwork := TO_CHAR(FLOOR(ipint / subnet + is_end) * subnet - is_end, 'FM0xxxxxxxxxxxxxxx');
-- Re-integrate
IF is_big = 0 THEN iphalf2 := ipwork;
ELSE iphalf1 := ipwork;
END IF;
RETURN SUBSTR(iphalf1 || iphalf2, 1, 32);
END nettohex;
-- WHERE clause:
-- 1. BETWEEN compare:
-- iptohex(a.ip_addy) BETWEEN nettohex(b.net_addy, b.cidr, 0) AND nettohex(b.net_addy, b.cidr, 1)
--
-- Requires three function-based indexes, but all of them would work, as they are all inside the tables.
--
-- 2. CIDR match:
-- nettohex(a.ip_addy, b.cidr) = nettohex(b.net_addy, b.cidr)
--
-- Only two functions and uses exact match, but first one requires an outside variable. Last one would be only function-based index.
-- An FBI of iptohex(a.ip_addy) could be implemented, but it's questionable if nettohex would use that index.
--
-- Recommended FBIs:
--
-- (SUBSTR(iptohex(a.ip_addy), 1, 32))
-- (SUBSTR(nettohex(b.ip_addy, b.cidr, 0), 1, 32), SUBSTR(nettohex(b.ip_addy, b.cidr, 1), 1, 32))
--
-- NOTE: Will need to use the SUBSTR form for the above WHERE clauses!
UPDATE: Oracle 11g does allow for the SUBSTR entry to be put a virtual column. So, you could have columns like this:
ip VARCHAR2(39),
cidr NUMBER(2),
ip_hex AS (SUBSTR(iptohex(ip), 1, 32)) VIRTUAL,
ip_nethex_start AS (SUBSTR(nettohex(ip, cidr, 0), 1, 32)) VIRTUAL,
ip_nethex_end AS (SUBSTR(nettohex(ip, cidr, 1), 1, 32)) VIRTUAL,
And indexes like:
CREATE INDEX foobar_iphex_idx ON foobar (ip_hex);
CREATE INDEX foobar_ipnet_idx ON foobar (ip_nethex_start, ip_nethex_end);
Using WHERE clauses like:
a.ip_hex BETWEEN b.ip_nethex_start AND b.ip_nethex_end
nettohex(a.ip, b.cidr) = b.ip_nethex_start -- not as effective

Related

Using a variable with Substr in PLSQL

I am trying to use a variable to set the length of a substr command.
If I explicitly set say 0,13 the code works fine however, I want to be able to use a variable.
Near the end of the code is the area of question:
:new.DOCNO := substr(:new.DESCRIP,0,15); -- success ... but limited
:new.DOCNO := substr(:new.DESCRIP,0,n_len); -- fail
The code overall is looking to find a number pattern from a file name and place it into another column.
Example: HR_1000-0001_A This is a file.pdf
I have tried various data types: integer, number,... etc.
The code compiles but fails when I run the trigger.
CREATE OR REPLACE TRIGGER set_doc_number
BEFORE UPDATE ON external_doc
FOR EACH ROW
WHEN (
new.STATUS = 'Released'
-- current document# is either '' or is ONLY a number
AND (
new.DOC_LIBRARY_ID = ''
OR
REGEXP_LIKE(new.DOC_LIBRARY_ID,'(\d+)')
)
)
DECLARE
-- init variables
b_update boolean;
n_len number := 0;
BEGIN
-- reset for each loop
b_update := FALSE;
-- check if criteria is met
-- AAA_####-####_AA
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 15;
END IF;
-- AA_####-####_AA
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 14;
END IF;
-- AAA_####-####_A
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 15;
END IF;
-- AA_####-####_A
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z]_(\d{4})-(\d{4})_[A-Z])') THEN
b_update := TRUE;
n_len := 14;
END IF;
-- AAA_####-####
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z][A-Z]_(\d{4})-(\d{4}))') THEN
b_update := TRUE;
n_len := 13;
END IF;
-- AA_####-####
IF b_update = FALSE AND REGEXP_LIKE(:new.DESCRIP,'([A-Z][A-Z]_(\d{4})-(\d{4}))') THEN
b_update := TRUE;
n_len := 12;
END IF;
-- can we update?
IF b_update = TRUE THEN
-- update
:new.DOCNO := substr(:new.DESCRIP,0,n_len);
END IF;
END;
I'm not sure why using n_len fails, it looks fine to me. PL/SQL indexes start at 1 and not 0, but it works fine if you use 0, so that's not an issue.
If you're going to use regexp functions, I'd suggest just using regexp_substr:
CREATE OR REPLACE TRIGGER set_doc_number
BEFORE UPDATE ON external_doc
FOR EACH ROW
WHEN (
new.STATUS = 'Released'
-- current document# is either '' or is ONLY a number
AND (
new.DOC_LIBRARY_ID = ''
OR
REGEXP_LIKE(new.DOC_LIBRARY_ID,'(\d+)')
)
)
DECLARE
-- init variables
v_docno varchar2(20);
BEGIN
-- look for a str with (2-3 alpha)_####-####(optional underscore and 1-3 alpha)
v_docno := regexp_substr(:new.DESCRIP,'[A-Z][A-Z][A-Z]?_\d{4}-\d{4}(_[A-Z][A-Z]?[A-Z]?)?');
-- can we update?
IF v_docno is not null THEN
-- update
:new.DOCNO := v_docno;
END IF;
END;

convert blob(image) to varchar in oracle

I am trying to read blob (image) from an oracle db and display it in html. The image is larger than the buffer size so I have to split it first and then append all the sub-strings. My approach is as below (there will be a loop to go through the blob):
SELECT utl_raw.cast_to_varchar2(dbms_lob.substr(FILE_CONTENTS,2000,1)) as mystring from doc where file_name='test.png'
The problem is that the converted string looks scrambled
I did not specify the char_set for converting, could that be the reason? If so, how can I know which one to use?
Thanks.
Here is a function to convert a BLOB into a Base64 string:
FUNCTION EncodeBASE64(InBlob IN BLOB) RETURN CLOB IS
BlobLen INTEGER := DBMS_LOB.GETLENGTH(InBlob);
read_offset INTEGER := 1;
amount INTEGER := 1440; -- must be a whole multiple of 3
-- size of a whole multiple of 48 is beneficial to get NEW_LINE after each 64 characters
buffer RAW(1440);
res CLOB := EMPTY_CLOB();
BEGIN
IF InBlob IS NULL OR NVL(BlobLen, 0) = 0 THEN
RETURN NULL;
ELSIF BlobLen <= 24000 THEN
RETURN UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(InBlob));
ELSE
-- UTL_ENCODE.BASE64_ENCODE is limited to 32k, process in chunks if bigger
LOOP
EXIT WHEN read_offset >= BlobLen;
DBMS_LOB.READ(InBlob, amount, read_offset, buffer);
res := res || UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(buffer));
read_offset := read_offset + amount;
END LOOP;
END IF;
RETURN res;
END EncodeBASE64;

oracle PL/SQL how to calculate range ip for IPv6 cidr

ex.IPv6 address with CIDR:
2620:0:2d0:200::7/32
out put
Start Range: 2620:0:0:0:0:0:0:0
End Range: 2620:0:ffff:ffff:ffff:ffff:ffff:ffff
how to calculate with PL/SQL ?
Once I wrote a general PL/SQL Package where you can do such conversions. It works for both, IPv4 and IPv6.
CREATE OR REPLACE PACKAGE IP_Util AS
/**
* Convert an IP-Address into decimal value.
* #param IP The IP-Address, e.g. '10.151.20.224' or '1080::8:800:200C:417A'.
* Supports also mixed notation like '0:0:0:0:0:FFFF:129.144.52.38'. CIDR value (e.g. '1080::8:800:200C:417A/80') is ignored.
* #return The decimal equivalent
*/
FUNCTION IP2Decimal(IP IN VARCHAR2) RETURN NUMBER DETERMINISTIC;
/**
* Convert an IP-Address into RWA value.
* #param IP The IP-Address, e.g. '10.151.20.224' or '1080::8:800:200C:417A'.
* Supports also mixed notation like '0:0:0:0:0:FFFF:129.144.52.38'. CIDR value (e.g. '1080::8:800:200C:417A/80') is ignored.
* #param ver IP version, either 4 or 6. If NULL then function determines the IP version.
* #return The RAW equivalent
*/
FUNCTION IP2RAW(IP IN VARCHAR2, ver IN INTEGER DEFAULT NULL) RETURN RAW DETERMINISTIC;
/**
* Convert an IP-Address from decimal value into IPv4 or IPv6 format.
* #param ip Decimal IP-Address, 0..(2**32)-1 or 0..(2**128)-1
* #param ver IP version, either 4 or 6
* #return The IP in IPv4 or IPv6 format
*/
FUNCTION Decimal2IP(ip IN NUMBER, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC;
/**
* Convert an IP-Address from RAW value into IPv4 or IPv6 format.
* #param ip RAW value of IP-Address, 0..FFFFFFFF or 0..FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
* #param ver IP version, either 4 or 6
* #return The IP in IPv4 or IPv6 format
*/
FUNCTION RAW2IP(ip IN RAW, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns SubnetMask of given IP-Subnet in CIDR notation.
* #param Ip Subnet IP-Address with CIDR notation, e.g. '10.152.10.17/24' or '1080::8:800:200C:417A/60'
* #return SubnetMask Subnet mask of IP-Subnet, e.g. '255.255.255.0' or 'ffff:ffff:ffff::'
*/
FUNCTION SubnetMask(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Network prefix of given IP-Subnet. In IPv4 this address was called subnet address.
* #param Ip IP-Address of subnet, e.g. '10.152.10.17' or '1080:0:100:8:800:200C:FFFF:417A'
* #param SubnetMask Subnet mask of subnet, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
* #return Network prefix, i.e. the first address from subnet, for example '10.152.0.0' or '1080:0:100:8:800:200C:FFFF::'
*/
FUNCTION NetworkPrefix(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Network prefix of given IP-Subnet. In IPv4 this address was called subnet address.
* #param Ip IP-Subnet with CIDR notation, e.g. '10.152.10.17/24' or '1080:0:100:8:800:200C:FFFF:417A/60'
* #return Network prefix, i.e. the first address from subnet, for example '10.152.0.0' or '1080:0:100:8:800:200C:FFFF::'
*/
FUNCTION NetworkPrefix(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Broadcast address of given IP-Subnet.
* IPv6 does not provide Broadcast anymore. However, function supports IPv6 for internal purpose.
* #param Ip IP-Address of subnet, e.g. '10.152.10.17' or '1080:0:100:8:800:200C:FFFF:417A'
* #param SubnetMask Subnet mask of subnet, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
* #return Broadcast address, i.e. the last address from subnet, for example '10.152.10.255' or '1080:0:100:8:800:ffff:ffff:ffff'
*/
FUNCTION BroadcastIp(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Broadcast address of given IP-Subnet.
* IPv6 does not provide Broadcast anymore. However, function supports IPv6 for internal purpose.
* #param Ip IP-Subnet with CIDR notation, e.g. '10.152.10.17/24' or '1080:0:100:8:800:200C:FFFF:417A/60'
* #return Broadcast address, i.e. the last address from subnet, for example '10.152.10.255' or '1080:0:100:8:800:ffff:ffff:ffff'
*/
FUNCTION BroadcastIp(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Translate Subnet mask to CIDR.
* #param SubnetMask Subnet mask of subnet, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
* #return CIDR value, e.g. 26
*/
FUNCTION SubnetMask2CIDR(SubnetMask VARCHAR2) RETURN INTEGER RESULT_CACHE DETERMINISTIC;
/**
* Translate CIDR to Subnet mask in IPv4 or IPv6 format.
* #param CIDR Length of network prefix
* #param ver IP version, either 4 or 6
* #return Subnet mask, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
*/
FUNCTION CIDR2SubnetMask(CIDR IN INTEGER, ver IN INTEGER) RETURN VARCHAR2 RESULT_CACHE DETERMINISTIC;
/**
* Returns full uncompressed IPv6 Address. Mainly used for internal purpose like conversion, storage, comparison, etc.
* '::' is replaced by zero pads, leading '0' are inserted (if leadingZero = TRUE), converted to lower cases.
* #param Ip Compact IPv6-Address (with CIDR or without CIDR, e.g. 2620:0:2D0:A2A2::7)
* #param leadingZero If TRUE then bit fields are padded with '0' in order to have always 4 characters
* #return The full IPv6 Address with 8 x 16 bits, e.g. '2620:0000:02d0:a2a2:0000:0000:0000:0007'
*/
FUNCTION UncompressIpV6(Ip IN VARCHAR2, leadingZero IN BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 DETERMINISTIC;
/**
* Makes an canonical IPv6 address according to RFC 5952, i.e. human readable.
* #param IPv6 IPv6-Address (with or without '::', with or without leading '0')
* #return Canonical IPv6 Address, e.g. 2620:0:2d0:200::7
*/
FUNCTION Canonical_IPv6(IPv6 IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
END IP_Util;
/
CREATE OR REPLACE PACKAGE BODY IP_Util AS
NUMERIC_OVERFLOW EXCEPTION;
PRAGMA EXCEPTION_INIT(NUMERIC_OVERFLOW, -1426);
FUNCTION IP2Decimal(IP IN VARCHAR2) RETURN NUMBER DETERMINISTIC IS
DecimalIp NUMBER; -- INTEGER does not cover (2**128)-1
BEGIN
IF REGEXP_LIKE(IP, ':') THEN
-- IPv6 Address
IF REGEXP_LIKE(IP, '\d+\.\d+\.\d+\.\d+') THEN
-- Mixed notation, e.g.: 0:0:0:0:0:FFFF:129.144.52.38
SELECT SUM(TO_NUMBER(REGEXP_SUBSTR(UncompressIpV6(IP), '[[:xdigit:]]+', 1, LEVEL), 'XXXX') * POWER(65536, 8-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 6;
SELECT DecimalIp + SUM(REGEXP_SUBSTR(REGEXP_SUBSTR(UncompressIpV6(IP), '\d+\.\d+\.\d+\.\d+'), '\d+', 1, LEVEL) * POWER(256, 4-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 4;
RETURN DecimalIp;
ELSE
SELECT SUM(TO_NUMBER(REGEXP_SUBSTR(UncompressIpV6(IP), '[[:xdigit:]]+', 1, LEVEL), 'XXXX') * POWER(65536, 8-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 8;
RETURN DecimalIp;
END IF;
ELSE
-- IPv4 Address
SELECT SUM(REGEXP_SUBSTR(IP, '\d+', 1, LEVEL) * POWER(256, 4-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 4;
RETURN DecimalIp;
END IF;
END IP2Decimal;
FUNCTION IP2RAW(IP IN VARCHAR2, ver IN INTEGER DEFAULT NULL) RETURN RAW DETERMINISTIC IS
BEGIN
IF ver IS NULL THEN
IF REGEXP_LIKE(IP, ':') THEN
RETURN IP2RAW(IP, 6);
ELSE
RETURN IP2RAW(IP, 4);
END IF;
ELSE
IF ver = 6 THEN
RETURN HEXTORAW(LPAD(TO_CHAR(IP2Decimal(ip), 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'), 32, '0'));
ELSIF ver = 4 THEN
RETURN HEXTORAW(LPAD(TO_CHAR(IP2Decimal(ip), 'fmXXXXXXXX'), 8, '0'));
ELSE
RAISE VALUE_ERROR;
END IF;
END IF;
END IP2RAW;
FUNCTION RAW2IP(ip IN RAW, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC IS
res VARCHAR2(45);
BEGIN
-- Range check "TO_NUMBER(ip, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') < 2**32, resp 2**128" not needed, because RAW values are usually not based on error-prone user input
IF ver = 4 THEN
-- Take only last 32 bit from RAW value with UTL_RAW.SUBSTR(ip, -4)
SELECT LISTAGG(TO_NUMBER(SUBSTR(SUBSTR(LPAD(RAWTOHEX(UTL_RAW.SUBSTR(ip, -4)), 8, '0'), -8), 2*LEVEL-1, 2), 'XX'), '.') WITHIN GROUP (ORDER BY LEVEL)
INTO res
FROM DUAL
CONNECT BY LEVEL <= 4;
RETURN res;
ELSIF ver = 6 THEN
RETURN Canonical_IPv6(SUBSTR(REGEXP_REPLACE(LPAD(RAWTOHEX(ip), 32, '0'), '([[:xdigit:]]{4})', ':\1'), 2));
ELSE
RAISE VALUE_ERROR;
END IF;
END RAW2IP;
FUNCTION Decimal2IP(ip IN NUMBER, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC IS
res VARCHAR2(45);
BEGIN
IF ip IS NULL THEN
RETURN NULL;
END IF;
IF ver = 4 THEN
IF ip > 2**32 - 1 THEN
RAISE NUMERIC_OVERFLOW;
END IF;
SELECT LISTAGG(TO_NUMBER(SUBSTR(LPAD(TO_CHAR(ip, 'fmXXXXXXXX'), 8, '0'), 2*LEVEL-1, 2), 'XX'), '.') WITHIN GROUP (ORDER BY LEVEL)
INTO res
FROM dual
CONNECT BY LEVEL <= 4;
RETURN res;
ELSIF ver = 6 THEN
IF ip > 2**128 - 1 THEN
RAISE NUMERIC_OVERFLOW;
END IF;
res := LPAD(TO_CHAR(ip, 'fmxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'),32, '0');
RETURN Canonical_IPv6(SUBSTR(REGEXP_REPLACE(res, '([[:xdigit:]]{4})', ':\1'), 2));
ELSE
RAISE VALUE_ERROR;
END IF;
END Decimal2IP;
FUNCTION SubnetMask(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
IF Ip IS NULL OR NOT REGEXP_LIKE(Ip, '/\d{1,3}$') THEN
RETURN NULL;
END IF;
IF REGEXP_LIKE(Ip, ':') THEN
RETURN CIDR2SubnetMask(REGEXP_SUBSTR(Ip, '\d{1,3}$'), 6);
ELSE
RETURN CIDR2SubnetMask(REGEXP_SUBSTR(Ip, '\d{1,2}$'), 4);
END IF;
END SubnetMask;
FUNCTION NetworkPrefix(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
IF REGEXP_LIKE(ip, ':') THEN
RETURN RAW2IP(UTL_RAW.BIT_AND(Ip2RAW(Ip, 6), Ip2RAW(SubnetMask, 6)), 6);
ELSE
RETURN RAW2IP(UTL_RAW.BIT_AND(Ip2RAW(Ip, 4),Ip2RAW(SubnetMask, 4)), 4);
END IF;
END NetworkPrefix;
FUNCTION NetworkPrefix(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN NetworkPrefix(REGEXP_REPLACE(Ip, '/\d{1,3}$'), SubnetMask(Ip));
END NetworkPrefix;
FUNCTION BroadcastIp(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
Subnet RAW(16);
SubnetInv RAW(16);
BEGIN
IF REGEXP_LIKE(ip, ':') THEN
Subnet := UTL_RAW.BIT_AND(Ip2RAW(Ip, 6), Ip2RAW(SubnetMask, 6));
SubnetInv := UTL_RAW.BIT_COMPLEMENT(Ip2RAW(SubnetMask, 6));
RETURN RAW2IP(UTL_RAW.BIT_OR(Subnet, SubnetInv), 6);
ELSE
Subnet := UTL_RAW.BIT_AND(Ip2RAW(Ip, 4), Ip2RAW(SubnetMask, 4));
SubnetInv := UTL_RAW.BIT_COMPLEMENT(Ip2RAW(SubnetMask, 4));
RETURN RAW2IP(UTL_RAW.BIT_OR(Subnet, SubnetInv), 4);
END IF;
END BroadcastIp;
FUNCTION BroadcastIp(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN BroadcastIp(REGEXP_REPLACE(Ip, '/\d{1,3}$'), SubnetMask(Ip));
END BroadcastIp;
FUNCTION SubnetMask2CIDR(SubnetMask VARCHAR2) RETURN INTEGER RESULT_CACHE DETERMINISTIC IS
ip RAW(16);
cidr INTEGER;
BEGIN
IF SubnetMask IS NULL THEN
RETURN NULL;
END IF;
IF REGEXP_LIKE(SubnetMask, ':') THEN
ip := IP2RAW(SubnetMask, 6);
cidr := 128-LOG(2, TO_NUMBER(UTL_RAW.BIT_COMPLEMENT(ip), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')+1);
ELSE
ip := IP2RAW(SubnetMask, 4);
cidr := 32-LOG(2, TO_NUMBER(UTL_RAW.BIT_COMPLEMENT(ip), 'XXXXXXXX')+1);
END IF;
RETURN cidr;
END SubnetMask2CIDR;
FUNCTION CIDR2SubnetMask(CIDR IN INTEGER, ver IN INTEGER) RETURN VARCHAR2 RESULT_CACHE DETERMINISTIC IS
BEGIN
IF CIDR IS NULL THEN
RETURN NULL;
END IF;
IF ver = 4 THEN
IF CIDR NOT BETWEEN 0 AND 32 THEN
RAISE VALUE_ERROR;
END IF;
RETURN RAW2IP(UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**(32-cidr)-1, 'fmXXXXXXXX'),8 , '0'))), 4);
ELSIF ver = 6 THEN
IF CIDR NOT BETWEEN 0 AND 128 THEN
RAISE VALUE_ERROR;
END IF;
RETURN RAW2IP(UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**(128-cidr)-1, 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'))), 6);
ELSE
RAISE VALUE_ERROR;
END IF;
END CIDR2SubnetMask;
FUNCTION UncompressIpV6(Ip IN VARCHAR2, leadingZero IN BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 DETERMINISTIC IS
IpFull VARCHAR2(50);
len INTEGER := 7;
TYPE VARCHAR_TABLE_TYPE IS TABLE OF VARCHAR2(10);
BitFields VARCHAR_TABLE_TYPE;
cidr VARCHAR2(5);
BEGIN
IF NOT REGEXP_LIKE(Ip, ':') THEN
RETURN Ip;
END IF;
cidr := REGEXP_SUBSTR(Ip, '/\d{1,3}$');
IpFull := REGEXP_REPLACE(Ip, '/\d{1,3}$');
IF REGEXP_LIKE(IpFull, '::') THEN
IpFull := REGEXP_REPLACE(REGEXP_REPLACE(IpFull, '^::', '0::'), '::$', '::0');
IF REGEXP_LIKE(IpFull, ':\d+\.\d+\.\d+\.\d+') THEN
-- Mixed notation, e.g.: 2002::FFFF:129.144.52.38
len := 6;
END IF;
WHILE REGEXP_COUNT(IpFull, ':') <= len LOOP
IpFull := REGEXP_REPLACE(IpFull, '::', ':0::');
END LOOP;
IpFull := REGEXP_REPLACE(IpFull, '::', ':');
END IF;
IF NOT leadingZero THEN
RETURN LOWER(IpFull||cidr);
END IF;
SELECT REGEXP_SUBSTR(IpFull, '[^:]+', 1, LEVEL)
BULK COLLECT INTO BitFields
FROM dual
CONNECT BY REGEXP_SUBSTR(IpFull, '[^:]+', 1, LEVEL) IS NOT NULL;
IpFull := LPAD(BitFields(1), 4, '0');
FOR i IN 2..BitFields.COUNT LOOP
IF REGEXP_LIKE(BitFields(i), '\d+\.\d+\.\d+\.\d+') THEN
IpFull := IpFull ||':'||BitFields(i);
ELSE
IpFull := IpFull ||':'||LPAD(BitFields(i), 4, '0');
END IF;
END LOOP;
RETURN LOWER(IpFull)||cidr;
END UncompressIpV6;
FUNCTION Canonical_IPv6(IPv6 IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
res VARCHAR2(50);
cidr VARCHAR2(5);
BEGIN
IF NOT REGEXP_LIKE(IPv6, ':') THEN
RETURN IPv6;
ELSIF REGEXP_LIKE(IPv6, '::') THEN
-- Do not shorten twice
res := UncompressIpV6(IPv6, FALSE);
ELSE
-- RFC 5952 section-4.3
res := LOWER(IPv6);
END IF;
-- Split CIDR if existing
cidr := REGEXP_SUBSTR(res, '/\d{1,3}$');
res := REGEXP_REPLACE(res, '/\d{1,3}$');
-- remove leading '0', RFC 5952 section-4.1
res := REGEXP_REPLACE(res, '(:|^)0+([[:xdigit:]]+)', '\1\2');
WITH ip AS
-- split IP into 16-bit fields
(SELECT REGEXP_SUBSTR(res, '[^:]+', 1, LEVEL) AS val, LEVEL AS pos
FROM DUAL
CONNECT BY REGEXP_SUBSTR(res, '[^:]+', 1, LEVEL) IS NOT NULL),
p AS
-- find consecutive (at least 2) 0 fields, RFC 5952 section-4.2.2
(SELECT pos, len, match_num
FROM ip
MATCH_RECOGNIZE (
ORDER BY pos
MEASURES
FINAL COUNT(*) AS len,
MATCH_NUMBER() AS match_num
ALL ROWS PER MATCH
PATTERN(zero{2,})
DEFINE zero AS val = '0')
),
m AS
-- select longest run of consecutive 0 fields, RFC 5952 section-4.2.3
(SELECT * FROM p WHERE len = (SELECT MAX(len) FROM p)),
f AS
-- select first sequence of longest run of consecutive 0 fields, RFC 5952 section-4.2.3
(SELECT * FROM m WHERE match_num = (SELECT MIN(match_num) FROM m))
SELECT REGEXP_REPLACE(LISTAGG(NVL2(match_num, ':', val), ':') WITHIN GROUP (ORDER BY pos), ':{2,}', '::')
INTO res
FROM ip
LEFT OUTER JOIN f USING (pos);
RETURN res||cidr;
END Canonical_IPv6;
END IP_Util;
/
Then you can use it for example like this:
SELECT
IP_Util.NetworkPrefix('2620:0:2d0:200::7/32'),
IP_Util.BroadcastIp('2620:0:2d0:200::7/32')
FROM dual;
2620:: 2620:0:ffff:ffff:ffff:ffff:ffff:ffff
If you prefer 2620:0:0:0:0:0:0:0 then use
IP_Util.UncompressIpV6(IP_Util.NetworkPrefix('2620:0:2d0:200::7/32'), false)
However, according RFC 5952 2620:: would be the preferred format.
Here are a few examples how this package can be used:
-- Determine if (IPv4) Address is a Private IP:
CREATE OR REPLACE FUNCTION IsPrivate_IP(ip IN VARCHAR2) RETURN NUMBER DETERMINISTIC IS
BEGIN
IF IP_Util.NetworkPrefix('10.0.0.0', '255.0.0.0') = IP_Util.NetworkPrefix(ip, '255.0.0.0') THEN
RETURN 1;
ELSIF IP_Util.NetworkPrefix('172.16.0.0', '255.240.0.0') = IP_Util.NetworkPrefix(ip, '255.240.0.0') THEN
RETURN 1;
ELSIF IP_Util.NetworkPrefix('192.168.0.0', '255.255.0.0') = IP_Util.NetworkPrefix(ip, '255.255.0.0') THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END IsPrivate_IP;
Or a more complex one which translates an IPv4 into IPv6 or vice versa using 6to4 and 6RD Network Prefix:
CREATE OR REPLACE FUNCTION NAT64(ip IN VARCHAR2, IpV6mask IN VARCHAR2 DEFAULT '::ffff:0:0') RETURN VARCHAR2 DETERMINISTIC IS
shift INTEGER;
cidr INTEGER;
n NUMBER;
a RAW(16);
b RAW(16);
BEGIN
IF REGEXP_LIKE(ip, ':') THEN
-- Translate from IPv6 to IPv4
IF NOT REGEXP_LIKE(IpV6mask, '/\d+{1,3}$') THEN
RETURN IP_Util.RAW2IP(UTL_RAW.BIT_AND(IP_Util.IP2Raw(ip), HEXTORAW('000000000000000000000000FFFFFFFF')), 4);
ELSE
shift := 128 - REGEXP_SUBSTR(IpV6mask, '\d+{1,3}$');
IF shift < 32 THEN
RAISE VALUE_ERROR;
END IF;
-- Generate mask for IPv4 address, e.g. '0000000000000000FFFFFFFF00000000'
b := HEXTORAW(LPAD(TO_CHAR((2**shift-1) - (2**(shift-32)-1), 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'));
n := TO_NUMBER(UTL_RAW.BIT_AND(IP_Util.IP2Raw(ip), b), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
-- UTL_RAW.SUBSTR does not work because you can shift only Bytes, I need Bits
RETURN IP_Util.Decimal2IP(TRUNC(n / 2**(shift-32)), 4);
END IF;
ELSE
-- Translate from IPv4 to IPv6
IF NOT REGEXP_LIKE(IpV6mask, '/\d+{1,3}$') THEN
a := UTL_RAW.BIT_AND(IP_Util.IP2Raw(IpV6mask), UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**32-1 , 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'))));
RETURN IP_Util.RAW2IP(UTL_RAW.BIT_OR(a, IP_Util.IP2RAW(ip, 6)), 6);
ELSE
cidr := REGEXP_SUBSTR(IpV6mask, '\d+{1,3}$');
shift := 128 - 32 - cidr;
IF shift < 0 THEN
RAISE VALUE_ERROR;
END IF;
a := UTL_RAW.BIT_AND(IP_Util.IP2Raw(IpV6mask), UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**(128-cidr)-1 , 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'))));
b := HEXTORAW(LPAD(TO_CHAR(2**shift * IP_Util.IP2Decimal(ip), 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'));
RETURN IP_Util.RAW2IP(UTL_RAW.BIT_OR(a, b), 6);
END IF;
END IF;
END NAT64;

Oracle socket connection

As you people can see I've been struggling with C++ for the past few years and one of the things was using C++ to open a socket connection then build up a string (where each letter is converted to hex) then a checksum is calculated, i really dont know how, then the string is sent out via the socket to a ploycomp signboard and the socket is closed.
The C++ socket connection and string building is very finicky and if its just a little off then nothing gets sent. Now the problem is that we are re-writing the C+ program into Apex and we want to transfer the socket connection to the oracle 11g database where all the signboard port/IP's and addresses are stored. Naturally it fell upon me to do this and I have no idea how to do this. I know there is a TCP componet to sql but no clue as to the actual code. Can any of you geniuses out there provide me with some help?
I was given this but am bit baffled as to what it means:
PolyComp (Pty) Ltd.
Designers & Manufacturers of Programmable Electronic Displays,
Colour Graphic Boards & Electro-magnetic Displays.
HEAD OFFICE & FACTORY Tel: 011-608-2770
PolyComp House Fax: 011-608-2774
4 Fountain Road E-mail: polycomp#global.co.za
Edenvale
P.O.Box 3234
Edenvale 1610
South Africa
STATIC SERIAL COMMUNICATION PROTOCOL
PolyComp displays can be linked to a computer through a serial communication RS232 , RS485 and
CURRENT LOOP using the following Default settings :
BAUD RATE : 9600
START BIT : 8
STOP BITS : 1
PARITY : NONE
8 Bit ASCII code compatible with extended IBM ASCII code .
These are the default settings which can be changed using the Dip-Switch on the SERIAL
COMMUNICATION BOARD .
CONNECTING THE COMPUTER TO THE SIGN
When you power up – the sign will perform a self test and display the results on the screen . The setting of the
“Serial Communication Board” will also be displayed . Be sure to use the same settings on the PC.
 Using RS232
A Direct connection via RS232 can be used only for a short distance – up to approximately : 100 metres .
Only 3 wires are used and these must connect as follows :
PC SIDE S I G N S I D E
DIN…. 25 DIN…. 9 Round DIN…. 5
Signal
TXD PIN 2 PIN 3 PIN 5 RXD
RXD PIN 3 PIN 2 PIN 3 TXD
GND PIN 7 PIN 5 PIN 4 GND
 Using RS485
Connect RS232 to the RS485 converter , between the PC and the sign .
This international standard is more suitable for long distance communication , as well as Multidrop
communication . {Few signs can be connected on Two-Way communication to the Host [ PC ] }
RS485 CONNECTIONS
PC RS232 RS232 / RS485 RS485 Up To 1000 Metres PolyComp Sign
(4 Wires) Converter ( 2 Wires + Screen ) With RS485
Interface

The PC is linked to the RS485 converter via 4 wires namely :~
TXD Transit Data from PC .
RXD Received Data to PC .
DTR Controls Traffic flow : + 12V => TX Mode , - 12 V => RX Mode .
GND Ground
Since RS485 only uses 2 wires , only one device can send data at a time .
The PC downloads a page of text , the sign checks the integrity of the message and if it is OK – it will transfer
the page from the temporary RX Buffer into the message buffer .
If the acknowledge BIT on the Serial Status Byte is set (B3) , then the sign will send “Acknowledge” back to
the PC .
In order for the PC to get the reply {when RS485 is used } DTR needs to be at - 12V [RX Mode ] . Once the
reply has been sent , the PC can send the next page ..etc…
When the last page has been sent , be sure that BIT 2 – on the “Serial Status Byte” has been Reset ( = 0 ), so
that the new message will be displayed immediately . When this BIT is set , it instructs the sign that more
pages are ‘coming’ and therefore , the sign will ‘wait’ for these pages and will not display the new message
…however if no new data is received within 40 seconds . It will consider the last valid page -received as the
last page and the sign will then display the new message .
COMMUNICATION PROTOCOL
These signs can be programmed for one way or two way communication by setting the relevant bit as explained
below ( Should a two way communication be selected , be sure that your hardware can suppport it ! ) .
The message should be sent to the sign page after page .
PROTOCOL
Byte Description Decimal Value Comments
1 Header Sync 00
2 Nol / Static ?? (Number Of Text
Lines On the Sign / 'S')
3 Sign Address XX (1 – 127,0 = “All Call”)
4 Etx 03 End Of Header .
[ Message text]
EOT 04 End Of Text
Last Byte Checksum ??
( The checksum is an EXCLUSIVE ‘OR’ function bit by bit starting at the SYNC. Up to and including the
“End Of Text” byte 04 ) .
Change Letter Appearance On The Display
In Order to change the appearance of letters on the display , the following controls can be used :~
A control block consists of 2 bytes :
1st Byte : Control Code = 28 BCD. [1C Hex]
2nd Byte : Command .
The following Commands are available :~
F : Flash Characters .
E : Enlarge Characters
R : Change Colour to Red ( Colour Signs Only! )
G : Change Colour To Green ( Colour Signs Only! )
Y : Change Colour to Yellow ( Colour Signs Only! )
M : Multicolour – i.e: Top Red Center Yellow Bottom Green ( Colour Signs Only! )
D : Return To Default Setting – i.e: Normal {Not Enlarged} Red Letters Not Flashing .
Every page starts with the default settings : Red , Normal Letters , No Flash !
EXAMPLE : The following string ~
“POLYCOMP” (28) “G IS” (28) “F” (28) “M THE” (28) “F” (28) “E BEST” WILL
DISPLAY : “POLYCOMP IS THE BEST”
“POLYCOMP” - RED ~ “THE” MULTI COLOUR FLASH .
“IS” - GREEN ~ “BEST” MULTI COLOUR ENLARGE .
IN THIS EXAMPLE ~
TEXT BETWEEN INVERTED COMMA’S “ ” IS ASCII CHARACTERS . NUMBERS BETWEEN
BRACKETS ARE IN bcd VALUES .
PolyComp (Pty) Ltd . Tel: 011 452 – 3505/6
Fax: 011 452 – 2542
EXAMPLE OF GTX PROGRAM FOR 1 LINE DISPLAY ~ GTXC
10 OPEN “COM1 : 9600,N,8,2,CS,DS,CD”AS #1
15 CLS : PRINT TAB(30) “ POLYCOMP TEST GTX”
16 PRINT TAB(28) “~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~”
20 A$=CHR$(0)+CHR$(1)+CHR$(2)+CHR$(3)+CHR$(&HC0)+“001”+CHR$(&HE2)+CHR$(&HC1)+CHR$(&HC0)
25 FOR CNT=100 TO 10000
30 MSG$= “CNT =”+STR$(CNT)
40 TXT$=A$+MSG$+CHR$(4)
50 CK=0
60 FOR I = 1 TO LEN(TXT$)
70 CK=CK XOR ASC(MID$(TXT$,I,1))
80 NEXT I
90 PRINT #1,TXT$;CHR$(CK);:LOCATE 5,34:PRINT “CNT =”;CNT
100 FOR D=1 TO 5000:NEXT D
110 NEXT CNT
EXAMPLE OF GTX PROGRAM FOR DOUBLE LINE DISPLAY ~ GTXC
10 OPEN “COM1 : 9600,N,8,2,CS,DS,CD”AS #1
15 CLS : PRINT TAB(30) “ POLYCOMP TEST GTX”
16 PRINT TAB(28) “~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~”
20 A$=CHR$(0)+CHR$(2)+CHR$(0)+CHR$(3)+CHR$(&HCO)+“001”+CHR$(&HE2)+CHR$(&HC1)+CHR$(&HC0)
26 FOR CNT=100 TO 10000
30 MSG$= “CNT =”+STR$(CNT)
41 TXT$=A$+MSG$+CHR$(4)
51 CK=0
61 FOR I = 1 TO LEN(TXT$)
71 CK=CK XOR ASC(MID$(TXT$,I,1))
81 NEXT I
91 PRINT #1,TXT$;CHR$(CK);:LOCATE 5,34:PRINT “CNT =”;CNT
101 FOR D=1 TO 5000:NEXT D
110 NEXT CNT
I have tried:
declare
bt_conn UTL_TCP.connection;
retval BINARY_INTEGER;
-- l_sequence VARCHAR2 (50) := '005301035204';
l_sequence VARCHAR2 (300) := '450000625EDC4000800669D20A44B20F0A6B6B290AEA1F40B07906E7A715E4BC5018FAF0F0D20000005301031C468656C6C6F2020202020202020202020 20202020202020201C442020202020202020202020202020202020202020202020200417';
BEGIN
bt_conn :=
UTL_TCP.open_connection (remote_host => '10.107.107.41',
remote_port => 8000,
tx_timeout => 15);
DBMS_LOCK.SLEEP (1);
retval := UTL_TCP.write_line (bt_conn, l_sequence);
-- DBMS_LOCK.SLEEP (1);
UTL_TCP.flush (bt_conn);
UTL_TCP.close_connection (bt_conn);
EXCEPTION
WHEN OTHERS
THEN
raise_application_error (-20101, SQLERRM);
UTL_TCP.close_connection (bt_conn);
END;
/
the string in l_sequence is a string captured via a packet sniffer when using a program by the signboard manufactures to send a string "HELLO"
but it doesnt seem to work
I managed to get this to print the letter 'I'
declare
bt_conn UTL_TCP.connection;
retval binary_integer;
--l_sequence raw(32000) := hextoraw('000100031C4748454C4C4F041F');
--l_sequence raw(32000) := hextoraw('005300031C44490445');
l_sequence raw(32000) := hextoraw('005301031C444920202020202020202020202020202020202020202020201C44202020202020202020202020202020202020202020202020043C');
begin
bt_conn := UTL_TCP.open_connection (
remote_host => '10.107.107.41',
remote_port => 8000,
in_buffer_size => 0,
out_buffer_size => 0,
tx_timeout => 10
);
begin
retval := UTL_TCP.write_raw (bt_conn, l_sequence, utl_raw.length(l_sequence));
UTL_TCP.close_connection (bt_conn);
exception
when others then begin
UTL_TCP.close_connection (bt_conn);
raise;
end;
end;
end;
/
Going to try and write some code now to pad the string, and then perform the checksum. Then i may get a little brave and try and print more than one letter.
Thanks for your help ThinkJet :)
UPDATE
I wrote the following to do the checksum
function append_bitwise_xor(p_packet in raw) return raw is
w_packet raw(32000) := hextoraw(p_packet);
w_sub_packet raw(32000) := null;
w_str_1 raw(32000) := null;
BEGIN
w_sub_packet := w_packet;
w_str_1 := substr(w_packet, 1, 2);
w_sub_packet := substr(w_sub_packet, 3, length(w_sub_packet) - 2);
while length(w_sub_packet) > 0 loop
w_str_1 := utl_raw.bit_xor(w_str_1, substr(w_sub_packet, 1, 2));
w_sub_packet := substr(w_sub_packet, 3, length(w_sub_packet) - 2);
end loop;
--- w_str_1 is the checksum
return w_packet || w_str_1;
END;
I also padded the messages with blank spaces, to match the packets that I caught better
function pad_line(p_text in varchar2, p_columns in number) return raw is
w_text varchar2(2000) := null;
w_raw_text raw(32000) := null;
w_columns number;
BEGIN
w_text := p_text;
select rawtohex(w_text) into w_raw_text from dual;
w_columns := p_columns;
while length(w_raw_text) < (2 * w_columns) loop
select w_raw_text || rawtohex(' ') into w_raw_text from dual;
end loop;
return w_raw_text;
END;
Thanks again for your assistance ThinkJet - it really helped us out :)
My suggestion is that you send string representation of bytes instead of bytes themselves.
So, try to change
retval := UTL_TCP.write_line (bt_conn, l_sequence);
to
retval := UTL_TCP.write_raw (bt_conn, hextoraw(l_sequence));
My only doubt is about space symbol in example data, but I assumed that he appears there accidentally.
With help of sniffer you can intercept a data which comes from Oracle and compare it with data from old utility.
Update (removed - incorrect protocol implementation)
Update 2
Below is implementation of protocol for Polycomp dislpays, described in documents, found in internet sources (link1, link2, link3).
Even if implementation won't work for you without modifications it contains much of useful code to learn how to deal with raw values in Oracle.
Utility package header:
create or replace package POLYCOMP_UTIL is
-- PolyComp signboard interaction for Oracle
-- SERST (Serial status flag byte #5 constants) --------------------
-- B1 = 1 => "Interrupt Mode" = 0 => Normal DISPLAY Mode.
C_SERST_INTERRUPT constant binary_integer := 2; -- 00000010
-- B2 = 1 => More pages to come , = 0 => Last page.
C_SERST_MORE_PAGES constant binary_integer := 4; -- 00000100
-- B3 = 1 => "ACK" Requested , = 0 => No "ACK" required.
C_SERST_ACK constant binary_integer := 8; -- 00001000
-- B4 = 1 Schedule Mode Operation = 0 => No schedule operation.
C_SERST_SCHEDULE constant binary_integer := 16; --00010000
--------------------------------------------------------------------
-- Scheduling constants for pages ----------------------------------
-- days of the week
C_SCHEDULE_MONDAY constant binary_integer := 1; -- 00000001
C_SCHEDULE_TUESDAY constant binary_integer := 2; -- 00000010
C_SCHEDULE_WEDNESDAY constant binary_integer := 4; -- 00000100
C_SCHEDULE_THURSDAY constant binary_integer := 8; -- 00001000
C_SCHEDULE_FRIDAY constant binary_integer := 16; -- 00010000
C_SCHEDULE_SATURDAY constant binary_integer := 32; -- 00100000
C_SCHEDULE_SUNDAY constant binary_integer := 64; -- 01000000
C_SCHEDULE_ALL_WEEK_DAYS constant binary_integer := 128; -- 10000000
--------------------------------------------------------------------
-- freezing time (TEMP0) constants ---------------------------------
-- also defines scroll speed for bottom line (1- fastest, 9 - slowest)
C_TEMP0_CONST_BITS constant raw(1) := hextoraw('C0'); -- 11000000
C_TEMP0_AND_MASK constant raw(1) := hextoraw('3F'); -- 00111111
C_SCROLL_SPEED_1 constant binary_integer := 1; -- 0000 0001
C_SCROLL_SPEED_2 constant binary_integer := 2; -- 0000 0010
C_SCROLL_SPEED_3 constant binary_integer := 3; -- 0000 0011
C_SCROLL_SPEED_4 constant binary_integer := 4; -- 0000 0100
C_SCROLL_SPEED_5 constant binary_integer := 5; -- 0000 0101
C_SCROLL_SPEED_6 constant binary_integer := 6; -- 0000 0110
C_SCROLL_SPEED_7 constant binary_integer := 7; -- 0000 0111
C_SCROLL_SPEED_8 constant binary_integer := 8; -- 0000 1001
C_SCROLL_SPEED_9 constant binary_integer := 9; -- 0000 1001
C_DURATION_2_SEC constant binary_integer := C_SCROLL_SPEED_1;
C_DURATION_5_SEC constant binary_integer := C_SCROLL_SPEED_2;
C_DURATION_10_SEC constant binary_integer := C_SCROLL_SPEED_3;
C_DURATION_20_SEC constant binary_integer := C_SCROLL_SPEED_4;
C_DURATION_30_SEC constant binary_integer := C_SCROLL_SPEED_5;
C_DURATION_45_SEC constant binary_integer := C_SCROLL_SPEED_6;
C_DURATION_60_SEC constant binary_integer := C_SCROLL_SPEED_7;
C_DURATION_90_SEC constant binary_integer := C_SCROLL_SPEED_8;
C_DURATION_120_SEC constant binary_integer := C_SCROLL_SPEED_9;
-- Constants for page schedule override
C_PAGE_STATUS_TIMER constant binary_integer := 0; -- 00 00 0000
C_PAGE_STATUS_ON constant binary_integer := 16; -- 00 01 0000
C_PAGE_STATUS_OFF constant binary_integer := 32; -- 00 10 0000
--------------------------------------------------------------------
-- Function constants for pages ------------------------------------
C_DISPLAY_MODE_RANDOM constant binary_integer := 0; -- 0000 0000
C_DISPLAY_MODE_APPEAR constant binary_integer := 1; -- 0000 0001
C_DISPLAY_MODE_WIPE constant binary_integer := 2; -- 0000 0010
C_DISPLAY_MODE_OPEN constant binary_integer := 3; -- 0000 0011
C_DISPLAY_MODE_LOCK constant binary_integer := 4; -- 0000 0100
C_DISPLAY_MODE_ROTATE constant binary_integer := 5; -- 0000 0101
C_DISPLAY_MODE_RIGHT constant binary_integer := 6; -- 0000 0110
C_DISPLAY_MODE_LEFT constant binary_integer := 7; -- 0000 0111
C_DISPLAY_MODE_ROLL_UP constant binary_integer := 8; -- 0000 1000
C_DISPLAY_MODE_ROLL_DOWN constant binary_integer := 9; -- 0000 1001
C_DISPLAY_MODE_PING_PONG constant binary_integer := 10; -- 0000 1010
C_DISPLAY_MODE_FILL_UP constant binary_integer := 11; -- 0000 1011
C_DISPLAY_MODE_PAINT constant binary_integer := 12; -- 0000 1100
C_DISPLAY_MODE_FADE_IN constant binary_integer := 13; -- 0000 1101
C_DISPLAY_MODE_JUMP constant binary_integer := 14; -- 0000 1110
C_DISPLAY_MODE_SLIDE constant binary_integer := 15; -- 0000 1111
-- display clock on top line
C_DISPLAY_CLOCK constant binary_integer := 16; -- 00010000
-- display temperature on top line
C_DISPLAY_TEMPERATURE constant binary_integer := 32; -- 00100000
--------------------------------------------------------------------
-- Status constants for pages --------------------------------------
C_LINES_1_2_JOIN_BOLD constant binary_integer := 1; -- 0000 0001
C_LINES_3_4_JOIN_BOLD constant binary_integer := 2; -- 0000 0010
C_LINES_5_6_JOIN_BOLD constant binary_integer := 4; -- 0000 0100
C_LINES_7_8_JOIN_BOLD constant binary_integer := 8; -- 0000 1000
C_AUTOCENTER_TEXT constant binary_integer := 16; -- 0001 0000
C_FOREIGN_LANGUAGE constant binary_integer := 32; -- 0010 0000
C_BACKGROUND_COLOR_ON constant binary_integer := 64; -- 0010 0000
--------------------------------------------------------------------
-- Function returns escape sequence for switching to flash text.
function StartFlashText return varchar2;
-- Function returns escape sequence for switching to enlarged text.
function StartEnlargedText return varchar2;
-- Function returns escape sequence for switching text color to red.
function StartRedText return varchar2;
-- Function returns escape sequence for switching text color to green.
function StartGreenText return varchar2;
-- Function returns escape sequence for switching text color to yellow.
function StartYellowText return varchar2;
-- Function returns escape sequence for switching to multicolored text
function StartMulticolorText return varchar2;
-- Function returns escape sequence for resetting text output parameters
-- to defaults: not enlarged, not flashing, red color
function ResetToDefaultText return varchar2;
-- Function produces text for page header with given parameters
-- %param pSignNumber Signboard address, 1 to 127 - for concrete signboard, 0 - send to all connected boards
-- %param pLineCount Number of lines on page to fit signboard, 1 - 255 (?)
-- %param pSerstFlags Serial status flags (SERST), combination of:
-- C_SERST_INTERRUPT -- Single exceptionally displayed page which interrupts normal schedule,
-- only one page can be sent in this mode.
-- C_SERST_MORE_PAGES -- is current page NOT last
-- C_SERST_ACK -- seems to be never used over tcp/ip
-- C_SERST_SCHEDULE -- There are scheduling information present for this page
-- %param pPageNumber Sequential page number, 0 - 999. Page number 0 reserved for time setting command.
-- %return Text to concatenate with text of the page, use as pHeaderText parameter for MakePage function.
function MakePageHeaderText(
pSignNumber in binary_integer,
pLineCount in binary_integer,
pSerstFlags in binary_integer,
pPageNumber in binary_integer
) return varchar2;
-- Returns text for Page 000 which sets system time on the board
-- %param pDate date and time to set
function MakeTimeSetPageText(pDate in date) return varchar2;
-- Return text for bytes 9-25 of regular page to describe scheduling information
-- %param pStart start date and time to display page
-- %param pEnd end date and time of displaying page
-- %param pWeekDays schedule page display by days of the week, combination of C_SCHEDULE_xxx constants
-- %return Text to use with pScheduleText parameter of MakePage function
function MakeScheduleText(
pStart in date,
pEnd in date,
pWeekDays in binary_integer
) return varchar2;
-- Returns text for TEMP0 byte which follows a header or scheduling information if specified
-- %param pDurationOrSpeed One of C_SCROLL_SPEED_x or C_DURATION_xxx constants. Set of constants
-- to choose from depends on C_DISPLAY_MODE_xxx specified for pDisplayMode
-- parameter of MakeDisplayPageMode function (C_DISPLAY_MODE_SLIDE or others)
-- %param pStatusMode One of C_PAGE_STATUS_xxx constants controlling page usage.
-- %return Text which must be included into page packet after scheduling information if cpecified
-- or header string
function MakePageTemp0(
pDurationOrSpeed in binary_integer,
pStatusMode in binary_integer
) return varchar2;
-- Function makes string byte which
-- %param pDisplayMode One of C_DISPLAY_MODE_xxx constants.
-- %param pDisplayClock True for display time on top of the screen
-- %param pDisplayTemperature True for display temperature on top of the screen
-- %return char which encodes function parameters and must be present after TEMP0 byte
function MakePageDisplayMode(
pDisplayMode in binary_integer,
pDisplayClock in boolean,
pDisplayTemperature in boolean
) return varchar2;
-- Function encodes given display mode parameters to one char.
-- %param pJoinLines12 Join 2 first lines toghether for BOLD mode
-- %param pJoinLines34 Lines 3 and 4 joined for BOLD mode
-- %param pJoinLines56 Lines 5 and 6 joined for BOLD mode
-- %param pJoinLines78 Lines 7 and 8 joined for BOLD mode
-- %param pAutoCenter Text willbe centered on the screen
-- %param pUseForeignLanguage If true then page in foreign (not English) language
-- %param pUseBackgroundColor If true turns ON background colour
function MakePageStatus(
pJoinLines12 in boolean,
pJoinLines34 in boolean,
pJoinLines56 in boolean,
pJoinLines78 in boolean,
pAutoCenter in boolean,
pUseForeignLanguage in boolean,
pUseBackgroundColor in boolean
) return varchar2;
-- Makes full page packet from header and text to send to device
-- %param pHeaderText Page header from MakePageHeaderText function
-- %param pScheduleText Page schedule settings from MakeScheduleText function.
-- Applicable only if C_SERST_SCHEDULE bit set in header
-- %param pPageTemp0 Duration/speed and status information encoded by MakePageTemp0 function
-- %param pDisplayMode Display mode information encoded by MakePageDisplayMode function
-- %param pPageStatus Page status modes encoded by MakePageStatus function
-- %param pPageText Text to display on the page with incorporated control bytes to switch
-- character color and appearence (C_xxx_TEXT)
-- %return Raw sequence of bytes including end-of-text flag and check sum,
-- which can be sent to device using SendPages() procedure
function MakePage(
pHeaderText in varchar2,
pScheduleText in varchar2,
pPageTemp0 in varchar2,
pDisplayMode in varchar2,
pPageStatus in varchar2,
pPageText in varchar2
) return raw;
-- Make full page to set time and date on signboard
-- %param pSignNumber Signboard address, 1 to 127 - for concrete signboard, 0 - send to all connected boards
-- %param pDateTime Date and time to set on signboard.
-- %param pIsLastPage Must be true if there are no other pages in the packet
-- %return full raw page ready to be sent with SendPages() procedure.
function MakeTimeSetPage(
pSignNumber in binary_integer,
pDateTime in date,
pIsLastPage in boolean
) return raw;
-- Procedure sends sequence of prepared pages to signboard through tcp/ip connection to WizNet device.
-- %param pHost Host address of WizNet device, e.g. '10.2.30.4'
-- %param pPort Port number of WizNet device, usually 8000
-- %param pPageSet Raw sequence of bytes to send. Contains concatenated output of MakePage() function.
-- Page with C_SERST_INTERRUPT flag set is a special case and must be sent alone.
procedure SendPages(
pHost in varchar2,
pPort in binary_integer,
pPageSet in raw
);
end;
/
Utility package body:
create or replace package body POLYCOMP_UTIL is
-- PolyComp signboard interaction for Oracle
-- Header Sync (byte #1)
C_HEADER_SYNC constant char(1) := chr(0);
-- Etx, End-of-Header (byte #4)
C_HEADER_END constant char(1) := chr(3);
-- End-of-text mark
C_END_OF_TEXT constant char(1) := chr(4);
-- SERST (Serial status flag byte #5 constants) --------------------
-- Mask with fixed bits
-- B0 , B5 = 0
-- B6 – B7 All = 1.
-- B7 B6 B5 B4 B3 B2 B1
-- 1 1 0 0 0 0 0
C_SERST_CONST_BITS constant raw(1) := hextoraw('C0'); -- 11000000
C_SERST_AND_MASK constant raw(1) := hextoraw('1E'); -- 00011110
--------------------------------------------------------------------
-- Function constants for pages ------------------------------------
C_FUNCTION_CONST_BITS constant raw(1) := hextoraw('C0'); -- 11000000
C_FUNCTION_AND_MASK constant raw(1) := hextoraw('3F'); -- 00111111
--------------------------------------------------------------------
-- Status constants for pages --------------------------------------
C_STATUS_CONST_BITS constant raw(1) := hextoraw('80'); -- 10000000
C_STATUS_AND_MASK constant raw(1) := hextoraw('7F'); -- 01111111
--------------------------------------------------------------------
-- Constants for changing text apperance with C_CONTROL symbol -----
-- Escape symbol for control commands in message text
C_CONTROL constant char(1) := chr(28);
-- flash following text
C_FLASH_TEXT char(2) := C_CONTROL || 'F';
-- enlarge following text
C_ENLARGE_TEXT char(2) := C_CONTROL || 'E';
-- display following text in red
C_RED_TEXT char(2) := C_CONTROL || 'R';
-- display following text in green
C_GREEN_TEXT char(2) := C_CONTROL || 'G';
-- display following text in yellow
C_YELLOW_TEXT char(2) := C_CONTROL || 'Y';
-- display following text with multiple color (e.g. top - red, center - yellow, bottom - green)
C_MULTICOLOR_TEXT char(2) := C_CONTROL || 'M';
-- Restore default settings: not enlarged, not flashing, red.
C_DEFAULT_TEXT char(2) := C_CONTROL || 'D';
--------------------------------------------------------------------
-- Function gets as parameter integer value which fits into 1 byte (0 <= pValue <= 255)
-- and converts it into raw(1)
function byte_to_raw(pValue in binary_integer) return raw
is
begin
return utl_raw.substr( utl_raw.cast_from_binary_integer(pValue), 4, 1 );
end;
-- specification in package header
function StartFlashText return varchar2
is
begin
return C_FLASH_TEXT;
end;
-- specification in package header
function StartEnlargedText return varchar2
is
begin
return C_ENLARGE_TEXT;
end;
-- specification in package header
function StartRedText return varchar2
is
begin
return C_RED_TEXT;
end;
-- specification in package header
function StartGreenText return varchar2
is
begin
return C_GREEN_TEXT;
end;
-- specification in package header
function StartYellowText return varchar2
is
begin
return C_YELLOW_TEXT;
end;
-- specification in package header
function StartMulticolorText return varchar2
is
begin
return C_MULTICOLOR_TEXT;
end;
-- specification in package header
function ResetToDefaultText return varchar2
is
begin
return C_DEFAULT_TEXT;
end;
-- specification in package header
function MakePageHeaderText(
pSignNumber in binary_integer,
pLineCount in binary_integer,
pSerstFlags in binary_integer,
pPageNumber in binary_integer
) return varchar2
is
vSerstValue raw(1);
begin
vSerstValue := byte_to_raw(pSerstFlags);
vSerstValue := utl_raw.bit_and(vSerstValue, C_SERST_AND_MASK);
vSerstValue := utl_raw.bit_or(VserstValue, C_SERST_CONST_BITS);
return C_HEADER_SYNC || chr(pLineCount) || chr(pSignNumber) || C_HEADER_END ||
utl_raw.cast_to_varchar2(vSerstValue)|| ltrim(to_char(pPageNumber, '009'));
end;
-- specification in package header
function MakeTimeSetPageText(pDate in date) return varchar2
is
begin
return to_char(pDate, 'hh24missddmmd');
end;
-- specification in package header
function MakeScheduleText(
pStart in date,
pEnd in date,
pWeekDays in binary_integer
) return varchar2
is
begin
return to_char(pStart,'ddmm') || to_char(pEnd, 'ddmm') ||
to_char(pStart,'hhmm') || to_char(pEnd,'hhmm') ||
chr(pWeekDays);
end;
-- specification in package header
function MakePageTemp0(
pDurationOrSpeed in binary_integer,
pStatusMode in binary_integer
) return varchar2
is
vValue raw(1);
begin
vValue := byte_to_raw(pDurationOrSpeed + pStatusMode);
vValue := utl_raw.bit_and(vValue, C_TEMP0_AND_MASK);
vValue := utl_raw.bit_or(vValue, C_TEMP0_CONST_BITS);
return utl_raw.cast_to_varchar2(vValue);
end;
-- specification in package header
function MakePageDisplayMode(
pDisplayMode in binary_integer,
pDisplayClock in boolean,
pDisplayTemperature in boolean
) return varchar2
is
vMode binary_integer;
vValue raw(1);
begin
vMode := pDisplayMode;
if( pDisplayClock ) then
vMode := vMode + C_DISPLAY_CLOCK;
end if;
if( pDisplayTemperature ) then
vMode := vMode + C_DISPLAY_TEMPERATURE;
end if;
vValue := byte_to_raw(vMode);
vValue := utl_raw.bit_and(vValue, C_FUNCTION_AND_MASK);
vValue := utl_raw.bit_or(vValue, C_FUNCTION_CONST_BITS);
return utl_raw.cast_to_varchar2(vValue);
end;
-- specification in package header
function MakePageStatus(
pJoinLines12 in boolean,
pJoinLines34 in boolean,
pJoinLines56 in boolean,
pJoinLines78 in boolean,
pAutoCenter in boolean,
pUseForeignLanguage in boolean,
pUseBackgroundColor in boolean
) return varchar2
is
vStatus binary_integer;
vValue raw(1);
begin
vStatus := 0;
if(pJoinLines12) then
vStatus := vStatus + C_LINES_1_2_JOIN_BOLD;
end if;
if(pJoinLines34) then
vStatus := vStatus + C_LINES_3_4_JOIN_BOLD;
end if;
if(pJoinLines56) then
vStatus := vStatus + C_LINES_5_6_JOIN_BOLD;
end if;
if(pJoinLines78) then
vStatus := vStatus + C_LINES_7_8_JOIN_BOLD;
end if;
if(pAutoCenter) then
vStatus := vStatus + C_AUTOCENTER_TEXT;
end if;
if(pUseForeignLanguage) then
vStatus := vStatus + C_FOREIGN_LANGUAGE;
end if;
if(pUseBackgroundColor) then
vStatus := vStatus + C_BACKGROUND_COLOR_ON;
end if;
vValue := byte_to_raw(vStatus);
vValue := utl_raw.bit_and(vValue, C_STATUS_AND_MASK);
vValue := utl_raw.bit_or(vValue, C_STATUS_CONST_BITS);
return utl_raw.cast_to_varchar2(vValue);
end;
-- specification in package header
function MakePage(
pHeaderText in varchar2,
pScheduleText in varchar2,
pPageTemp0 in varchar2,
pDisplayMode in varchar2,
pPageStatus in varchar2,
pPageText in varchar2
) return raw
is
vPage raw(32565);
vCheckSum raw(1) := hextoraw('00');
begin
vPage := utl_raw.cast_to_raw(
pHeaderText ||
pScheduleText || pPageTemp0 || pDisplayMode || pPageStatus ||
pPageText || C_END_OF_TEXT
);
for i in 1 .. utl_raw.length(vPage) loop
vCheckSum := utl_raw.bit_xor(vCheckSum, utl_raw.substr(vPage,i,1));
end loop;
return utl_raw.concat(vPage, vCheckSum);
end;
-- specification in package header
function MakeTimeSetPage(
pSignNumber in binary_integer,
pDateTime in date,
pIsLastPage in boolean
) return raw
is
vSerstValue binary_integer := C_SERST_SCHEDULE;
begin
if(not pIsLastPage) then
vSerstValue := vSerstValue + C_SERST_MORE_PAGES;
end if;
return MakePage(
pHeaderText => MakePageHeaderText(
pSignNumber => pSignNumber,
pLineCount => 0,
pSerstFlags => vSerstValue,
pPageNumber => 0 -- control page
),
pScheduleText => MakeTimeSetPageText(pDateTime),
pPageTemp0 => null,
pDisplayMode => null,
pPageStatus => null,
pPageText => null
);
end;
-- specification in package header
procedure SendPages(
pHost in varchar2,
pPort in binary_integer,
pPageSet in raw
)
is
C_CHUNK_SIZE constant binary_integer := 16*1024*8; -- 16 KB
vConn UTL_TCP.Connection;
vPagesLength binary_integer;
vRC binary_integer;
vTimeout binary_integer;
vBytesSent binary_integer;
vCurChunkSize binary_integer;
begin
vPagesLength := utl_raw.length(pPageSet);
-- Set timeout based on baud rate 9600 plus 256Kb tcp/ip network overhead and minimum at 10 seconds.
-- Calculating timeout such a way assumes that no significant buffering performed at receiver which
-- becomes true in case of WizNet device buffer overflow.
-- Seems that oracle ignores timeout for write operation in current versions but anyway
-- this must be taken into account.
vTimeout := greatest(10, ceil( (C_CHUNK_SIZE*8*1.04)/(256*1024) ) + ceil( (C_CHUNK_SIZE*8)/9600 ) );
vConn := UTL_TCP.open_connection (
remote_host => pHost,
remote_port => pPort,
in_buffer_size => 0,
out_buffer_size => 16*1024*8, -- 16KB
tx_timeout => vTimeout
);
begin
vBytesSent := 0;
while (vBytesSent < vPagesLength) loop
vCurChunkSize := least(C_CHUNK_SIZE, vPagesLength - vBytesSent);
vRC := UTL_TCP.write_raw(
vConn,
utl_raw.substr(pPageSet, vBytesSent+1, vCurChunkSize),
vCurChunkSize
);
if(vRC < vCurChunkSize) then
raise_application_error(-20001,
'Error sending chunk: only ' || vRC || ' bytes out of ' || vCurChunkSize || ' sent.'
);
end if;
vBytesSent := vBytesSent + vCurChunkSize;
end loop;
UTL_TCP.Flush(vConn);
UTL_TCP.close_connection(vConn);
exception
when others then begin
UTL_TCP.close_connection(vConn);
raise;
end;
end;
end;
end;
/
Example with single page displayed in interrupt mode:
declare
vHeader varchar2(10);
vSchedule varchar2(100);
vTemp0 varchar2(1);
vDisplayMode varchar2(1);
vStatus varchar2(1);
vText varchar2(32767);
vPage raw(32767);
begin
vHeader := polycomp_util.MakePageHeaderText(1,1, polycomp_util.C_SERST_INTERRUPT, 1);
vTemp0 := polycomp_util.MakePageTemp0(polycomp_util.C_SCROLL_SPEED_4, polycomp_util.C_PAGE_STATUS_ON);
vDisplayMode := polycomp_util.MakePageDisplayMode(polycomp_util.C_DISPLAY_MODE_SLIDE, false, false);
vStatus := polycomp_util.MakePageStatus(false, false, false, false, false, false, false);
vText := polycomp_util.StartGreenText || '<-- SLIDING green TEXT -->';
vPage := polycomp_util.MakePage(vHeader, null, vTemp0, vDisplayMode, vStatus, vText);
polycomp_util.SendPages(vPage);
end;

IP address stored as decimal - PL/SQL to display as dotted quad

We have an Oracle database that contains IP addresses stored as decimal integers - this is incredibly painful when manipulating the data by hand instead of via the web interface, yet hand manipulation is really handy as the network guys continually ask us to do strange things that the web interface authors did not anticipate.
Could someone provide me with the PL/SQL or other method to display these decimal IPs as dotted decimal i.e. 123.123.123.123 format?
I.e. I'd like to be able to run a query such as :
select hostname, inttoip(ip_address) from host;
and have the inttoip() procedure display ip_address as 203.30.237.2 instead of as 3407801602.
Ideally I'd like a procedure which provides the inverse function too, e.g.
insert into host (hostname,ip_address) values ('some-hostname', iptoint('203.30.237.2'));
I have perl to do this, but my PL/SQL/Oracle knowledge is not good enough to port it into PL/SQL.
Alternatively a way to run the perl as the procedural language within the oracle context analogous to the following in postgres:
CREATE FUNCTION perl_func (integer) RETURNS integer AS $$
<some perl>
$$ LANGUAGE plperl;
Would be great - if possible - probably even better as I could then do lots of procedural stuff within Oracle in a language I am familiar with.
This is the function you need:
create or replace
function inttoip(ip_address integer) return varchar2
deterministic
is
begin
return to_char(mod(trunc(ip_address/256/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256),256))
||'.'||to_char(mod(ip_address,256));
end;
(Comments about making function deterministic and using to_char taken on board - thanks).
In Oracle 11G you could make the formatted IP address a virtual column on the host table:
alter table host
add formatted_ip_address varchar2(15)
generated always as
( to_char(mod(trunc(ip_address/256/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256),256))
||'.'||to_char(mod(ip_address,256))
) virtual;
This column could then be indexed for queries if required.
Your query becomes:
select hostname, formatted_ip_address from host;
CREATE OR REPLACE
FUNCTION inttoip(ip_address IN INTEGER) RETURN VARCHAR2 IS
v8 VARCHAR2(8);
BEGIN
-- 1. convert the integer into hexadecimal representation
v8 := TO_CHAR(ip_address, 'FMXXXXXXXX');
-- 2. convert each XX portion back into decimal
RETURN to_number(substr(v8,1,2),'XX')
|| '.' || to_number(substr(v8,3,2),'XX')
|| '.' || to_number(substr(v8,5,2),'XX')
|| '.' || to_number(substr(v8,7,2),'XX');
END inttoip;
CREATE OR REPLACE
FUNCTION iptoint(ip_string IN VARCHAR2) RETURN INTEGER IS
d1 INTEGER;
d2 INTEGER;
d3 INTEGER;
q1 VARCHAR2(3);
q2 VARCHAR2(3);
q3 VARCHAR2(3);
q4 VARCHAR2(3);
v8 VARCHAR2(8);
BEGIN
-- 1. parse the input, e.g. '203.30.237.2'
d1 := INSTR(ip_string,'.'); -- first dot
d2 := INSTR(ip_string,'.',1,2); -- second dot
d3 := INSTR(ip_string,'.',1,3); -- third dot
q1 := SUBSTR(ip_string, 1, d1 - 1); -- e.g. '203'
q2 := SUBSTR(ip_string, d1 + 1, d2 - d1 - 1); -- e.g. '30'
q3 := SUBSTR(ip_string, d2 + 1, d3 - d2 - 1); -- e.g. '237'
q4 := SUBSTR(ip_string, d3 + 1); -- e.g. '2'
-- 2. convert to a hexadecimal string
v8 := LPAD(TO_CHAR(TO_NUMBER(q1),'FMXX'),2,'0')
|| LPAD(TO_CHAR(TO_NUMBER(q2),'FMXX'),2,'0')
|| LPAD(TO_CHAR(TO_NUMBER(q3),'FMXX'),2,'0')
|| LPAD(TO_CHAR(TO_NUMBER(q4),'FMXX'),2,'0');
-- 3. convert to a decimal number
RETURN TO_NUMBER(v8, 'FMXXXXXXXX');
END iptoint;
-- INET ATON en INET NTOA and helper function GET TOKEN
CREATE OR REPLACE function inet_ntoa (ip integer) return varchar2
is
ip1 integer;
ip2 integer;
ip3 integer;
ip4 integer;
ipi integer := ip;
begin
ip1 := floor(ipi/power(2,24));
ipi := ipi - (ip1*power(2,24));
ip2 := floor(ipi/power(2,16));
ipi := ipi - (ip2*power(2,16));
ip3 := floor(ipi/power(2,8));
ipi := ipi - (ip3*power(2,8));
ip4 := ipi;
return ip1||'.'||ip2||'.'||ip3||'.'||ip4;
end;
/
CREATE OR REPLACE FUNCTION get_token (the_list VARCHAR2,the_index NUMBER, delim VARCHAR2 := '.') RETURN VARCHAR2
IS
start_pos INTEGER;
end_pos INTEGER;
BEGIN
IF the_index = 1 THEN
start_pos := 1;
ELSE
start_pos := INSTR (the_list, delim, 1, the_index - 1);
IF start_pos = 0 THEN
RETURN NULL;
ELSE
start_pos := start_pos + LENGTH (delim);
END IF;
END IF;
end_pos := INSTR (the_list, delim, start_pos, 1);
IF end_pos = 0 THEN
RETURN SUBSTR (the_list, start_pos);
ELSE
RETURN SUBSTR (the_list, start_pos, end_pos - start_pos);
END IF;
END get_token;
/
CREATE OR REPLACE function inet_aton (ip varchar2) return integer
is
invalid_ip_adres exception;
pragma exception_init(invalid_ip_adres,-6502);
ipi integer;
begin
ipi := get_token(ip,4)
+(get_token(ip,3)*power(2,8))
+(get_token(ip,2)*power(2,16))
+(get_token(ip,1)*power(2,24));
return ipi;
exception
when invalid_ip_adres
then
return null;
end;
/

Resources