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;
Related
Support for Oracle Multimedia was dropped in Oracle 19c, so my code to extract dimensions from a JPEG image is throwing an error. Is there a workaround to this issue?
For Oracle 12, my code looked like this:
BEGIN
img := ORDSYS.ORDImage.init('FILE', my_dir, my_img_name);
img.setProperties();
w := img.getWidth();
h := img.getHeight();
EXCEPTION
WHEN OTHERS THEN
w := NULL;
h := NULL;
END;
Based on code found in a response to "Getting Image size of JPEG from its binary" (I'm not sure which language), I came up with this procedure:
PROCEDURE p_jpegstats(directory_in IN VARCHAR2,
filename_in IN VARCHAR2,
height_out OUT INTEGER,
width_out OUT INTEGER,
bpc_out OUT INTEGER, -- bits per channel
cps_out OUT INTEGER -- colors per component
) IS
file bfile;
pos INTEGER:=1;
h VARCHAR2(4);
w VARCHAR2(4);
mrkr VARCHAR2(2);
len VARCHAR2(4);
bpc VARCHAR2(2);
cps VARCHAR2(2);
-- Declare a quick helper procedure for readability
PROCEDURE next_byte(buf out varchar2, amt INTEGER:=1) IS
cnt INTEGER;
BEGIN
cnt := amt;
dbms_lob.read(file, cnt, pos, buf);
pos := pos + cnt;
END next_byte;
BEGIN
-- This code is based off of code found here: https://stackoverflow.com/a/48488655/3303651
-- Open the file
file := bfilename(directory_in, filename_in);
dbms_lob.fileopen(file);
-- Init the output variables in case something goes awry.
height_out := NULL;
width_out := NULL;
bpc_out := NULL;
cps_out := NULL;
LOOP
BEGIN
LOOP
next_byte(mrkr);
EXIT WHEN mrkr <> 'FF';
END LOOP;
CONTINUE WHEN mrkr = 'D8'; -- Start of image (SOI)
EXIT WHEN mrkr = 'D9'; -- End of image (EOI)
CONTINUE WHEN mrkr BETWEEN 'D0' AND 'D7';
CONTINUE WHEN mrkr = '01'; -- TEM
next_byte(len, 2);
IF mrkr = 'C0' THEN
next_byte(bpc); -- bits per channel
next_byte(h, 2); -- height
next_byte(w, 2); -- width
next_byte(cps); -- colors per component
EXIT;
END IF;
pos := pos + to_number(len, 'XXXX') - 2;
EXCEPTION WHEN OTHERS THEN EXIT; END;
END LOOP;
-- Write back the values we found
height_out := to_number(h, 'XXXX');
width_out := to_number(w, 'XXXX');
bpc_out := to_number(bpc, 'XX');
cps_out := to_number(cps, 'XX');
-- close out the file
dbms_lob.fileclose(file);
END p_jpegstats;
This will throw an error if the directory is invalid or the file can't be opened. If the outputs are NULL, then there was some other issue.
It's probably not the most efficient or elegant code (I'm not a pro with PL/SQL [yet!]), but it works. Here is an example usage:
DECLARE
h INTEGER;
w INTEGER;
bpc INTEGER;
cps INTEGER;
BEGIN
p_jpegstats('MY_DIR', 'my_image.jpg', h, w, bpc, cps);
DBMS_OUTPUT.PUT_LINE(w || ' x ' || h || ' ' || bpc || ' ' || cps);
END;
/
This ought to return something like
800 x 200 8 3
Edit: Removed unused variable.
Colleagues, hello. I need to call SOAP web service from Oracle DB where payload consists of file in base64 encoding. The problem is that i have limitation in payload size - 32 000 chars (varchar type). Examples, here and here. So it means that i can not call web service where payload is quite huge. Have anybody examples where payload increases the limitation of varchar2 type. Than you.
To go over the 32k characters limitation, you can try to use a CLOB with utl_http. Here is a procedure to do it :
PROCEDURE write_clob_to_http_request(pc_clob IN OUT NOCOPY CLOB)
IS
ln_offset NUMBER := 1;
ln_i NUMBER := 1;
ln_amount NUMBER := 32767;
ln_clob_length NUMBER := dbms_lob.getlength(pc_clob);
lv_buffer VARCHAR2(32767);
luh_http_request utl_http.req;
BEGIN
luh_http_request := utl_http.begin_request('http://SoapServiceLocation' , 'POST', 'HTTP/1.1');
utl_http.set_header(luh_http_request, 'Content-Type', lv_http_content_type);
utl_http.set_header(luh_http_request, 'Content-Length', LENGTH(pc_content));
utl_http.set_header(luh_http_request, 'SOAPAction', 'http://SoapServiceLocation');
IF dbms_lob.isopen(pc_clob) != 1 THEN
dbms_lob.open(pc_clob, 0);
END IF;
WHILE ( ln_offset < ln_clob_length )
LOOP
dbms_lob.read(pc_clob, ln_amount, ln_offset, lv_buffer);
UTL_HTTP.write_text(luh_http_request, lv_buffer);
ln_offset := ln_offset + ln_amount;
ln_i := ln_i + 1;
END LOOP;
IF dbms_lob.isopen(pc_clob) = 1 THEN
dbms_lob.close(pc_clob);
END IF;
luh_http_response := utl_http.get_response(luh_http_request);
utl_http.read_text(luh_http_response, pc_content);
utl_http.end_response(luh_http_response);
END write_clob_to_http_request;
You can easily create a CLOB by concatenating VARCHAR2 variables:
pc_content CLOB := TO_CLOB('a string') || TO_CLOB('another string)...;
I have been stuck with this issue now all morning. I actually saw this code here and decide to use it for our purpose here.
The issue that I am running into is that when we execute the code, sometimes it writes a file from db to the folder.
Other times, we get "numeric or value error"
Can any expert please help me fix it?
Here is the code I am using:
create or replace
PROCEDURE getfile(pfname VARCHAR2, display_name IN VARCHAR2)
IS
vblob BLOB;
vstart NUMBER := 1;
bytelen NUMBER := 32000;
len NUMBER;
my_vr RAW(32000);
x NUMBER;
v_name VARCHAR2(32760);
lv_str_len NUMBER;
l_output utl_file.file_type;
BEGIN
-- define output directory
--lv_str_len := Length(pfname);
--v_name := display_name||upper(substr(pfname,lv_str_len-3,lv_str_len));
v_name := display_name;
l_output := utl_file.Fopen('My_DIR', v_name, 'w', 32760);
-- get length of blob
SELECT dbms_lob.Getlength(FILENAME)
INTO len
FROM GENERAL.GUBFILE
WHERE gubfile_name = pfname;
-- dbms_output.put_line('Length: '||len);
-- save blob length
x := len;
-- select blob into variable
SELECT BLOBVALUE
INTO vblob
FROM FILES
WHERE filename = pfname;
-- if small enough for a single write
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.Put_raw(l_output, vblob);
utl_file.Fflush(l_output);
ELSE -- write in pieces
-- dbms_output.put_line('multi write '||vstart);
vstart := 1;
WHILE vstart < len LOOP
dbms_lob.READ(vblob, bytelen, vstart, my_vr);
utl_file.Put_raw(l_output, my_vr);
utl_file.Fflush(l_output);
-- set the start position for the next cut
vstart := vstart + bytelen;
-- set the end position if less than 32000 bytes
x := x - bytelen;
IF x < 32000 THEN
bytelen := x;
END IF;
END LOOP;
END IF;
dbms_output.Put_line('End');
utl_file.Fclose(l_output);
END getfile;
The exact error is:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "USER.GETFILE", line 40
ORA-06512: at line 8
The error comes from utl_file.put_raw.
The maximum size of the buffer parameter is 32767 bytes.
You check for IF len < 32760 THEN, however, I see no guarantee in your code, that the len variable actually holds the length of the vblob variable that is the buffer in the put_raw call.
So I suppose the vblob variable's actual length is longer then 32767 and that is the reason for the error.
Hence I suggest to delete this piece of code:
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.Put_raw(l_output, vblob);
utl_file.Fflush(l_output);
ELSE
also the END IF; of course, and always go for the 'write in pieces' branch.
I see now, you've done this based on the Burleson example which is good http://www.dba-oracle.com/t_writing_blob_clob_os_file.htm
but you see, unlike you, Burleson gets the len variable and the vblob variable from the same table and the same field.
-- get length of blob
SELECT dbms_lob.getlength(productblob)
INTO len
FROM products
WHERE id = product_id;
-- save blob length
x := len;
-- select blob into variable
SELECT product_blob
INTO vblob
FROM products
WHERE id = product_id;
EDIT
So an other option would be to fix the select for getting length. This means you'll have to replace this select:
-- get length of blob
SELECT dbms_lob.Getlength(FILENAME)
INTO len
FROM GENERAL.GUBFILE
WHERE gubfile_name = pfname;
with this:
-- get length of blob
SELECT dbms_lob.Getlength(BLOBVALUE)
INTO len
FROM FILES
WHERE filename = pfname;
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
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;
/